mirror of
https://github.com/systemd/systemd.git
synced 2024-10-31 07:51:21 +03:00
Merge pull request #19995 from poettering/cred-tool
Add support for encrypted credentials
This commit is contained in:
commit
19755bca19
57
TODO
57
TODO
@ -142,16 +142,37 @@ Features:
|
||||
|
||||
* expose MS_NOSYMFOLLOW in various places
|
||||
|
||||
* ability to insert trusted configuration and secrets into the boot parameters
|
||||
of a kernel booting in a VM or on baremetal some way, via TPM
|
||||
protection. idea:
|
||||
1. pass via /proc/bootconfig
|
||||
2. for secrets: put secrets in node of /proc/bootconfig, decrypt them via
|
||||
TPM early on in PID 1, put them in $CREDENTIAL_PATH logic
|
||||
3. for config: put signed data in node /proc/booconfig, validate via TPM
|
||||
early on in PID 1, put data into /run/bootconfig/ as individual files
|
||||
4. boot loader/stub should pick these up automatically from the boot loader
|
||||
file systems
|
||||
* allow passing creds into kernel when booting: in EFI stub, collect creds
|
||||
files from ESP directory, generate CPIO archive on the fly from them, so that
|
||||
they are dropped into /run/initramfs/creds/ and pass to kernel as additional
|
||||
initrd. Then, use LoadCredentialEncrypted=foo:/run/initramfs/creds/foo to
|
||||
load them.
|
||||
|
||||
* make LoadCredential= automatically find credentials in /etc/creds,
|
||||
/run/creds, … and so on, if path component is unqualified
|
||||
|
||||
* teach LoadCredential=/LoadCredentialEncrypted= to load credentials from
|
||||
kernel cmdline, maybe: LoadCredentialEncrypted=foobar:proc-cmdline:foobar
|
||||
|
||||
* credentials system:
|
||||
- acquire from kernel command line
|
||||
- acquire from EFI variable?
|
||||
- acquire via via ask-password?
|
||||
- acquire creds via keyring?
|
||||
- pass creds via keyring?
|
||||
- pass creds via memfd?
|
||||
- acquire + decrypt creds from pkcs11?
|
||||
- make systemd-cryptsetup acquire pw via creds logic
|
||||
- make PAMName= acquire pw via creds logic
|
||||
- make macsec/wireguard code in networkd read key via creds logic
|
||||
- make gatwayd/remote read key via creds logic
|
||||
- add sd_notify() command for flushing out creds not needed anymore
|
||||
|
||||
* teach LoadCredential= the ability to load all files from a specified dir as
|
||||
individual creds
|
||||
|
||||
* add tpm.target or so which is delayed until TPM2 device showed up in case
|
||||
firmware indicates there is one.
|
||||
|
||||
* tpm2: support a PIN policy, i.e. allowing windows-style short authentication
|
||||
passwords by using the TPM2 to enforce ratelimiting and such, use for
|
||||
@ -195,19 +216,6 @@ Features:
|
||||
- cryptsetup-generator: allow specification of passwords in crypttab itself
|
||||
- support rd.luks.allow-discards= kernel cmdline params in cryptsetup generator
|
||||
|
||||
* credentials system:
|
||||
- maybe add AcquireCredential= for querying a cred via ask-password
|
||||
- maybe try to acquire creds via keyring?
|
||||
- maybe try to pass creds via keyring?
|
||||
- maybe optionally pass creds via memfd
|
||||
- maybe add support for decrypting creds via TPM
|
||||
- maybe add support for decrypting/importing creds via pkcs11
|
||||
- make systemd-cryptsetup acquire pw via creds logic
|
||||
- make PAMName= acquire pw via creds logic
|
||||
- make macsec/wireguard code in networkd read key via creds logic
|
||||
- make gatwayd/remote read key via creds logic
|
||||
- add sd_notify() command for flushing out creds not needed anymore
|
||||
|
||||
* when configuring loopback netif, and it fails due to EPERM, eat up error if
|
||||
it happens to be set up alright already.
|
||||
|
||||
@ -223,9 +231,6 @@ Features:
|
||||
address as conduit for some minimal connection metainfo, and use it to
|
||||
restore the "description" logic that kdbus used to have.
|
||||
|
||||
* teach LoadCredential= the ability to load all files from a specified dir as
|
||||
individual creds
|
||||
|
||||
* systemd-analyze netif that explains predictable interface (or networkctl)
|
||||
|
||||
* Add service setting to run a service within the specified VRF. i.e. do the
|
||||
|
@ -2711,8 +2711,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(say) SetCredential = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(say) SetCredentialEncrypted = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(ss) LoadCredential = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(ss) LoadCredentialEncrypted = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly as SupplementaryGroups = ['...', ...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly s PAMName = '...';
|
||||
@ -3224,8 +3228,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
|
||||
|
||||
<!--property SetCredential is not documented!-->
|
||||
|
||||
<!--property SetCredentialEncrypted is not documented!-->
|
||||
|
||||
<!--property LoadCredential is not documented!-->
|
||||
|
||||
<!--property LoadCredentialEncrypted is not documented!-->
|
||||
|
||||
<!--property SupplementaryGroups is not documented!-->
|
||||
|
||||
<!--property PAMName is not documented!-->
|
||||
@ -3812,8 +3820,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="SetCredential"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="SetCredentialEncrypted"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LoadCredential"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LoadCredentialEncrypted"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="SupplementaryGroups"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="PAMName"/>
|
||||
@ -4511,8 +4523,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(say) SetCredential = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(say) SetCredentialEncrypted = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(ss) LoadCredential = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(ss) LoadCredentialEncrypted = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly as SupplementaryGroups = ['...', ...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly s PAMName = '...';
|
||||
@ -5052,8 +5068,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
|
||||
|
||||
<!--property SetCredential is not documented!-->
|
||||
|
||||
<!--property SetCredentialEncrypted is not documented!-->
|
||||
|
||||
<!--property LoadCredential is not documented!-->
|
||||
|
||||
<!--property LoadCredentialEncrypted is not documented!-->
|
||||
|
||||
<!--property SupplementaryGroups is not documented!-->
|
||||
|
||||
<!--property PAMName is not documented!-->
|
||||
@ -5638,8 +5658,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="SetCredential"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="SetCredentialEncrypted"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LoadCredential"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LoadCredentialEncrypted"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="SupplementaryGroups"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="PAMName"/>
|
||||
@ -6234,8 +6258,12 @@ node /org/freedesktop/systemd1/unit/home_2emount {
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(say) SetCredential = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(say) SetCredentialEncrypted = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(ss) LoadCredential = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(ss) LoadCredentialEncrypted = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly as SupplementaryGroups = ['...', ...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly s PAMName = '...';
|
||||
@ -6703,8 +6731,12 @@ node /org/freedesktop/systemd1/unit/home_2emount {
|
||||
|
||||
<!--property SetCredential is not documented!-->
|
||||
|
||||
<!--property SetCredentialEncrypted is not documented!-->
|
||||
|
||||
<!--property LoadCredential is not documented!-->
|
||||
|
||||
<!--property LoadCredentialEncrypted is not documented!-->
|
||||
|
||||
<!--property SupplementaryGroups is not documented!-->
|
||||
|
||||
<!--property PAMName is not documented!-->
|
||||
@ -7207,8 +7239,12 @@ node /org/freedesktop/systemd1/unit/home_2emount {
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="SetCredential"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="SetCredentialEncrypted"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LoadCredential"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LoadCredentialEncrypted"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="SupplementaryGroups"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="PAMName"/>
|
||||
@ -7924,8 +7960,12 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(say) SetCredential = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(say) SetCredentialEncrypted = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(ss) LoadCredential = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(ss) LoadCredentialEncrypted = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly as SupplementaryGroups = ['...', ...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly s PAMName = '...';
|
||||
@ -8379,8 +8419,12 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
|
||||
|
||||
<!--property SetCredential is not documented!-->
|
||||
|
||||
<!--property SetCredentialEncrypted is not documented!-->
|
||||
|
||||
<!--property LoadCredential is not documented!-->
|
||||
|
||||
<!--property LoadCredentialEncrypted is not documented!-->
|
||||
|
||||
<!--property SupplementaryGroups is not documented!-->
|
||||
|
||||
<!--property PAMName is not documented!-->
|
||||
@ -8869,8 +8913,12 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="SetCredential"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="SetCredentialEncrypted"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LoadCredential"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LoadCredentialEncrypted"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="SupplementaryGroups"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="PAMName"/>
|
||||
|
@ -833,6 +833,7 @@ manpages = [
|
||||
'8',
|
||||
['systemd-coredump.socket', 'systemd-coredump@.service'],
|
||||
'ENABLE_COREDUMP'],
|
||||
['systemd-creds', '1', [], ''],
|
||||
['systemd-cryptenroll', '1', [], 'HAVE_LIBCRYPTSETUP'],
|
||||
['systemd-cryptsetup-generator', '8', [], 'HAVE_LIBCRYPTSETUP'],
|
||||
['systemd-cryptsetup@.service',
|
||||
|
378
man/systemd-creds.xml
Normal file
378
man/systemd-creds.xml
Normal file
@ -0,0 +1,378 @@
|
||||
<?xml version='1.0'?> <!--*-nxml-*-->
|
||||
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
|
||||
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
|
||||
|
||||
<refentry id="systemd-creds"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refentryinfo>
|
||||
<title>systemd-creds</title>
|
||||
<productname>systemd</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>systemd-creds</refentrytitle>
|
||||
<manvolnum>1</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>systemd-creds</refname>
|
||||
<refpurpose>Lists, shows, encrypts and decrypts service credentials</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<cmdsynopsis>
|
||||
<command>systemd-creds</command>
|
||||
<arg choice="opt" rep="repeat">OPTIONS</arg>
|
||||
</cmdsynopsis>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para><command>systemd-creds</command> is a tool for listing, showing, encrypting and decrypting unit
|
||||
credentials. Credentials are limited-size binary or textual objects that may be passed to unit
|
||||
processes. They are primarily used for passing cryptographic keys (both public and private) or
|
||||
certificates, user account information or identity information from the host to services.</para>
|
||||
|
||||
<para>Credentials are configured in unit files via the <varname>LoadCredential=</varname>,
|
||||
<varname>SetCredential=</varname>, <varname>LoadCredentialEncrypted=</varname> and
|
||||
<varname>SetCredentialEncrypted=</varname> settings, see
|
||||
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
|
||||
details.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Commands</title>
|
||||
|
||||
<para>The following commands are understood:</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><command>list</command></term>
|
||||
|
||||
<listitem><para>Show a list of credentials passed into the current execution context. This command
|
||||
shows the files in the directory referenced by the <varname>$CREDENTIALS_DIRECTORY</varname>
|
||||
environment variable, and is intended to be executed from within service context.</para>
|
||||
|
||||
<para>Along with each credential name, the size and security state is shown. The latter is one of
|
||||
<literal>secure</literal> (in case the credential is backed by unswappable memory,
|
||||
i.e. <literal>ramfs</literal>), <literal>weak</literal> (in case it is backed by any other type of
|
||||
memory), or <literal>insecure</literal> (if having any access mode that is not 0400, i.e. if readable
|
||||
by anyone but the owner).</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>cat</command> <replaceable>credential...</replaceable></term>
|
||||
|
||||
<listitem><para>Show contents of specified credentials passed into the current execution
|
||||
context. Takes one or more credential names, whose contents shall be written to standard
|
||||
output.</para>
|
||||
|
||||
<para>When combined with <option>--json=</option> or <option>--transcode=</option> the output is
|
||||
transcoded in simple ways before outputting.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>setup</command></term>
|
||||
|
||||
<listitem><para>Generates a host encryption key for credentials, if none has been generated
|
||||
before. This ensures the <filename>/var/lib/systemd/credential.secret</filename> file is initialized
|
||||
with a random secret key if it doesn't exist yet. This secret key is used when encrypting/decrypting
|
||||
credentials with <command>encrypt</command> or <command>decrypt</command>, and is only accessible to
|
||||
the root user. Note that there's typically no need to invoke this command explicitly as it is
|
||||
implicitly called when <command>encrypt</command> is invoked, and credential host key encryption
|
||||
selected.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>encrypt</command> <replaceable>input</replaceable> <replaceable>output</replaceable></term>
|
||||
|
||||
<listitem><para>Loads the specified (unencrypted plaintext) input credential file, encrypts it and
|
||||
writes the (encrypted ciphertext) version to the specified output credential file. The resulting file
|
||||
may be referenced in the <varname>LoadCredentialEncrypted=</varname> setting in unit files, or its
|
||||
contents used literally in <varname>SetCredentialEncrypted=</varname> settings.</para>
|
||||
|
||||
<para>Takes two file system paths. The file name part of the output path is embedded as name in the
|
||||
encrypted credential, to ensure encrypted credentials cannot be renamed and reused for different
|
||||
purposes without this being noticed. The credential name to embed may be overridden with the
|
||||
<option>--name=</option> setting. The input or output paths may be specified as <literal>-</literal>,
|
||||
in which case the credential data is read from/written to standard input and standard output. If the
|
||||
output path is specified as <literal>-</literal> the credential name cannot be derived from the file
|
||||
system path, and thus should be specified explicitly via the <option>--name=</option> switch.</para>
|
||||
|
||||
<para>The credential data is encrypted symmetrically with one of the following encryption
|
||||
keys:</para>
|
||||
|
||||
<orderedlist>
|
||||
<listitem><para>A secret key automatically derived from the system's TPM2 chip. This encryption key
|
||||
is not stored on the host system and thus decryption is only possible with access to the original
|
||||
TPM2 chip. Or in other words, the credential secured in this way can only be decrypted again by the
|
||||
local machine.</para></listitem>
|
||||
|
||||
<listitem><para>A secret key stored in the <filename>/var/lib/systemd/credential.secret</filename>
|
||||
file which is only accessible to the root user. This "host" encryption key is stored on the host
|
||||
file system, and thus decryption is possible with access to the host file system and sufficient
|
||||
privileges. The key is automatically generated when needed, but can also be created explicitly with
|
||||
the <command>setup</command> command, see above.</para></listitem>
|
||||
|
||||
<listitem><para>A combination of the above: an encryption key derived from both the TPM2 chip and
|
||||
the host file system. This means decryption requires both access to the original TPM2 chip and the
|
||||
OS installation. This is the default mode of operation if a TPM2 chip is available and
|
||||
<filename>/var/lib/systemd/</filename> resides on persistent media.</para></listitem>
|
||||
</orderedlist>
|
||||
|
||||
<para>Which of the three keys shall be used for encryption may be configured with the
|
||||
<option>--with-key=</option> switch. Depending on the use-case for the encrypted credential the key to
|
||||
use may differ. For example, for credentials that shall be accessible from the initial RAM disk
|
||||
(initrd) of the system encryption with the host key is not appropriate since access to the host key
|
||||
is typically not available from the initrd. Thus, for such credentials only the TPM2 key should be
|
||||
used.</para>
|
||||
|
||||
<para>Encrypted credentials are always encoded in Base64.</para>
|
||||
|
||||
<para>Use <command>decrypt</command> (see below) to undo the encryption operation, and acquire the
|
||||
decrypted plaintext credential from the encrypted ciphertext credential.</para>
|
||||
|
||||
<para>The credential data is encrypted using AES256-GCM, i.e. providing both confidentiality and
|
||||
integrity, keyed by a SHA256 hash of one or both of the secret keys described above.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>decrypt</command> <replaceable>input</replaceable>
|
||||
<optional><replaceable>output</replaceable></optional></term>
|
||||
|
||||
<listitem><para>Undoes the effect of the <command>encrypt</command> operation: loads the specified
|
||||
(encrypted ciphertext) input credential file, decrypts it and writes the (decrypted plaintext)
|
||||
version to the specified output credential file.</para>
|
||||
|
||||
<para>Takes one or two file system paths. The file name part of the input path is compared with the
|
||||
credential name embedded in the encrypted file. If it does not match decryption fails. This is done
|
||||
in order to ensure that encrypted credentials are not re-purposed without this being detected. The
|
||||
credential name to compare with the embedded credential name may also be overridden with the
|
||||
<option>--name=</option> switch. If only one path is specified (or the output path specified as
|
||||
<literal>-</literal>) it is taken as input path and the decrypted credential is written to standard
|
||||
output. If the input path is specified as <literal>-</literal> the encrypted credential is read from
|
||||
standard input. In this mode, the expected name embedded in the credential cannot be derived from the
|
||||
path and should be specified explicitly with <option>--name=</option>.</para>
|
||||
|
||||
<para>Decrypting credentials requires access to the original TPM2 chip and/or credentials host key,
|
||||
see above. Information about which keys are required is embedded in the encrypted credential data,
|
||||
and thus decryption is entirely automatic.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<xi:include href="standard-options.xml" xpointer="help" />
|
||||
<xi:include href="standard-options.xml" xpointer="version" />
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Options</title>
|
||||
|
||||
<variablelist>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--system</option></term>
|
||||
|
||||
<listitem><para>When specified with the <command>list</command> and <command>cat</command> commands
|
||||
operates on the credentials passed to system as a whole instead of on those passed to the current
|
||||
execution context. This is useful in container environments where credentials may be passed in from
|
||||
the container manager.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--transcode=</option></term>
|
||||
|
||||
<listitem><para>When specified with the <command>cat</command> or <command>decrypt</command>
|
||||
commands, transcodes the output before showing it. Takes one of <literal>base64</literal>,
|
||||
<literal>unbase64</literal>, <literal>hex</literal> or <literal>unhex</literal> as argument, in order
|
||||
to encode/decode the credential data with Base64 or as series of hexadecimal values.</para>
|
||||
|
||||
<para>Note that this has no effect on the <command>encrypt</command> command, as encrypted
|
||||
credentials are unconditionally encoded in Base64.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--newline=</option></term>
|
||||
|
||||
<listitem><para>When specified with <command>cat</command> or <command>decrypt</command> controls
|
||||
whether to add a trailing newline character to the end of the output if it doesn't end in one,
|
||||
anyway. Takes one of <literal>auto</literal>, <literal>yes</literal> or <literal>no</literal>. The
|
||||
default mode of <literal>auto</literal> will suffix the output with a single newline character only
|
||||
when writing credential data to a TTY.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--pretty</option></term>
|
||||
<term><option>-p</option></term>
|
||||
|
||||
<listitem><para>When specified with <command>encrypt</command> controls whether to show the encrypted
|
||||
credential as <varname>SetCredentialEncrypted=</varname> setting that may be pasted directly into a
|
||||
unit file.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--name=</option><replaceable>name</replaceable></term>
|
||||
|
||||
<listitem><para>When specified with the <command>encrypt</command> command controls the credential
|
||||
name to embed in the encrypted credential data. If not specified the name is chosen automatically
|
||||
from the filename component of the specified output path. If specified as empty string no
|
||||
credential name is embedded in the encrypted credential, and no verification of credential name is
|
||||
done when the credential is decrypted.</para>
|
||||
|
||||
<para>When specified with the <command>decrypt</command> command control the credential name to
|
||||
validate the credential name embedded in the encrypted credential with. If not specified the name is
|
||||
chosen automatically from the filename component of the specified input path. If no credential name
|
||||
is embedded in the encrypted credential file (i.e. the <option>--name=</option> with an empty string
|
||||
was used when encrypted) the specified name has no effect as no credential name validation is
|
||||
done.</para>
|
||||
|
||||
<para>Embedding the credential name in the encrypted credential is done in order to protect against
|
||||
reuse of credentials for purposes they weren't originally intended for, under the assumption the
|
||||
credential name is chosen carefully to encode its intended purpose.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--timestamp=</option><replaceable>timestamp</replaceable></term>
|
||||
|
||||
<listitem><para>When specified with the <command>encrypt</command> command controls the timestamp to
|
||||
embed into the encrypted credential. Defaults to the current time. Takes a timestamp specification in
|
||||
the format described in
|
||||
<citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
|
||||
|
||||
<para>When specified with the <command>decrypt</command> command controls the timestamp to use to
|
||||
validate the "not-after" timestamp that was configured with <option>--not-after=</option> during
|
||||
encryption. If not specified defaults to the current system time.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--not-after=</option><replaceable>timestamp</replaceable></term>
|
||||
|
||||
<listitem><para>When specified with the <command>encrypt</command> command controls the time when the
|
||||
credential shall not be used anymore. This embeds the specified timestamp in the encrypted
|
||||
credential. During decryption the timestamp is checked against the current system clock, and if the
|
||||
timestamp is in the past the decryption will fail. By default no such timestamp is set. Takes a
|
||||
timestamp specification in the format described in
|
||||
<citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--with-key=</option></term>
|
||||
<term><option>-H</option></term>
|
||||
<term><option>-T</option></term>
|
||||
|
||||
<listitem><para>When specified with the <command>encrypt</command> command controls the encryption
|
||||
key to use. Takes one of <literal>host</literal>, <literal>tpm2</literal>,
|
||||
<literal>host+tpm2</literal> or <literal>auto</literal>. See above for details on the three key
|
||||
types. If set to <literal>auto</literal> (which is the default) the TPM2 key is used if a TPM2 device
|
||||
is found and not running in a container. The host key is used if
|
||||
<filename>/var/lib/systemd/</filename> is on persistent media. This means on typical systems the
|
||||
encryption is by default bound to both the TPM2 chip and the OS installation, and both need to be
|
||||
available to decrypt the credential again. If <literal>auto</literal> is selected but neither TPM2 is
|
||||
available (or running in container) nor <filename>/var/lib/systemd/</filename> is on persistent
|
||||
media, encryption will fail.</para>
|
||||
|
||||
<para>The <option>-H</option> switch is a shortcut for <option>--with-key=host</option>. Similar,
|
||||
<option>-T</option> is a shortcut for <option>-with-key=tpm2</option>.</para>
|
||||
|
||||
<para>When encrypting credentials that shall be used in the initial RAM disk (initrd) where
|
||||
<filename>/var/lib/systemd/</filename> is typically not available make sure to use
|
||||
<option>--with-key=tpm2</option> mode, to disable binding against the host secret.</para>
|
||||
|
||||
<para>This switch has no effect on the <command>decrypt</command> command, as information on which
|
||||
key to use for decryption is included in the encrypted credential already.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--tpm2-device=</option><replaceable>PATH</replaceable></term>
|
||||
|
||||
<listitem><para>Controls the TPM2 device to use. Expects a device node path referring to the TPM2
|
||||
chip (e.g. <filename>/dev/tpmrm0</filename>). Alternatively the special value <literal>auto</literal>
|
||||
may be specified, in order to automatically determine the device node of a suitable TPM2 device (of
|
||||
which there must be exactly one). The special value <literal>list</literal> may be used to enumerate
|
||||
all suitable TPM2 devices currently discovered.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--tpm2-pcrs=</option><arg rep="repeat">PCR</arg></term>
|
||||
|
||||
<listitem><para>Configures the TPM2 PCRs (Platform Configuration Registers) to bind the encryption
|
||||
key to. Takes a <literal>+</literal> separated list of numeric PCR indexes in the range 0…23. If not
|
||||
used, defaults to PCR 7 only. If an empty string is specified, binds the encryption key to no PCRs at
|
||||
all. For details about the PCRs available, see the documentation of the switch of the same name for
|
||||
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<xi:include href="standard-options.xml" xpointer="no-pager" />
|
||||
<xi:include href="standard-options.xml" xpointer="no-legend" />
|
||||
<xi:include href="standard-options.xml" xpointer="json" />
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Exit status</title>
|
||||
|
||||
<para>On success, 0 is returned.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Examples</title>
|
||||
|
||||
<example>
|
||||
<title>Encrypt a password for use as credential</title>
|
||||
|
||||
<para>The following command line encrypts the specified password <literal>hunter2</literal>, writing the result
|
||||
to a file <filename>password.cred</filename>.</para>
|
||||
|
||||
<programlisting># echo -n hunter2 | systemd-creds encrypt - password.cred</programlisting>
|
||||
|
||||
<para>This decrypts the file <filename>password.cred</filename> again, revealing the literal password:</para>
|
||||
|
||||
<programlisting># systemd-creds decrypt password.cred
|
||||
hunter2</programlisting>
|
||||
</example>
|
||||
|
||||
<example>
|
||||
<title>Encrypt a password and include it in a unit file</title>
|
||||
|
||||
<para>The following command line prompts the user for a password and generates a
|
||||
<varname>SetCredentialEncrypted=</varname> line from it for a credential named
|
||||
<literal>mysql-password</literal>, suitable for inclusion in a unit file.</para>
|
||||
|
||||
<programlisting># systemd-ask-password -n | systemd-creds encrypt --name=mysql-password -p - -
|
||||
🔐 Password: ****
|
||||
SetCredentialEncrypted=mysql-password: \
|
||||
k6iUCUh0RJCQyvL8k8q1UyAAAAABAAAADAAAABAAAAASfFsBoPLIm/dlDoGAAAAAAAAAA \
|
||||
NAAAAAgAAAAAH4AILIOZ3w6rTzYsBy9G7liaCAd4i+Kpvs8mAgArzwuKxd0ABDjgSeO5k \
|
||||
mKQc58zM94ZffyRmuNeX1lVHE+9e2YD87KfRFNoDLS7F3YmCb347gCiSk2an9egZ7Y0Xs \
|
||||
700Kr6heqQswQEemNEc62k9RJnEl2q7SbcEYguegnPQUATgAIAAsAAAASACA/B90W7E+6 \
|
||||
yAR9NgiIJvxr9bpElztwzB5lUJAxtMBHIgAQACCaSV9DradOZz4EvO/LSaRyRSq2Hj0ym \
|
||||
gVJk/dVzE8Uxj8H3RbsT7rIBH02CIgm/Gv1ukSXO3DMHmVQkDG0wEciyageTfrVEer8z5 \
|
||||
9cUQfM5ynSaV2UjeUWEHuz4fwDsXGLB9eELXLztzUU9nsAyLvs3ZRR+eEK/A==</programlisting>
|
||||
|
||||
<para>The generated line can be pasted 1:1 into a unit file, and will ensure the acquired password will
|
||||
be made available in the <varname>$CREDENTIALS_DIRECTORY</varname><filename>/mysql-password</filename>
|
||||
credential file for the started service.</para>
|
||||
|
||||
<para>Utilizing the unit file drop-in logic this can be used to securely pass a password credential to
|
||||
a unit. A similar, more comprehensive set of commands to insert a password into a service
|
||||
<filename>xyz.service</filename>:</para>
|
||||
|
||||
<programlisting># mkdir -p /etc/systemd/system/xyz.service.d
|
||||
# systemd-ask-password -n | systemd-creds encrypt --name=mysql-password -p - - > /etc/systemd/system/xyz.service.d/50-password.conf
|
||||
# systemctl daemon-reload
|
||||
# systemctl restart xyz.service</programlisting>
|
||||
</example>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
<para>
|
||||
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
</refentry>
|
@ -2338,7 +2338,8 @@ SystemCallErrorNumber=EPERM</programlisting>
|
||||
clients via D-Bus IPC, and generally not understood as being data that requires protection. Moreover,
|
||||
environment variables are propagated down the process tree, including across security boundaries
|
||||
(such as setuid/setgid executables), and hence might leak to processes that should not have access to
|
||||
the secret data. Use <varname>LoadCredential=</varname> (see below) to pass data to unit processes
|
||||
the secret data. Use <varname>LoadCredential=</varname>, <varname>LoadCredentialEncrypted=</varname>
|
||||
or <varname>SetCredentialEncrypted=</varname> (see below) to pass data to unit processes
|
||||
securely.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
@ -2832,6 +2833,7 @@ StandardInputData=SWNrIHNpdHplIGRhIHVuJyBlc3NlIEtsb3BzLAp1ZmYgZWVtYWwga2xvcHAncy
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>LoadCredential=</varname><replaceable>ID</replaceable><optional>:<replaceable>PATH</replaceable></optional></term>
|
||||
<term><varname>LoadCredentialEncrypted=</varname><replaceable>ID</replaceable><optional>:<replaceable>PATH</replaceable></optional></term>
|
||||
|
||||
<listitem><para>Pass a credential to the unit. Credentials are limited-size binary or textual objects
|
||||
that may be passed to unit processes. They are primarily used for passing cryptographic keys (both
|
||||
@ -2860,6 +2862,20 @@ StandardInputData=SWNrIHNpdHplIGRhIHVuJyBlc3NlIEtsb3BzLAp1ZmYgZWVtYWwga2xvcHAncy
|
||||
from the service manager into a service. This option may be used multiple times, each time defining
|
||||
an additional credential to pass to the unit.</para>
|
||||
|
||||
<para>The <varname>LoadCredentialEncrypted=</varname> setting is identical to
|
||||
<varname>LoadCredential=</varname>, except that the credential data is decrypted before being passed
|
||||
on to the executed processes. Specifically, the referenced path should refer to a file or socket with
|
||||
an encrypted credential, as implemented by
|
||||
<citerefentry><refentrytitle>systemd-creds</refentrytitle><manvolnum>1</manvolnum></citerefentry>. This
|
||||
credential is loaded, decrypted and then passed to the application in decrypted plaintext form, in
|
||||
the same way a regular credential specified via <varname>LoadCredential=</varname> would be. A
|
||||
credential configured this way may encrypted with a secret key derived from the system's TPM2
|
||||
security chip, or with a secret key stored in
|
||||
<filename>/var/lib/systemd/credentials.secret</filename>, or with both. Using encrypted credentials
|
||||
improves security as credentials are not stored in plaintext and only decrypted into plaintext the
|
||||
moment a service requiring them is started. Moreover, credentials may be bound to the local hardware
|
||||
and installations, so that they cannot easily be analyzed offline.</para>
|
||||
|
||||
<para>The credential files/IPC sockets must be accessible to the service manager, but don't have to
|
||||
be directly accessible to the unit's processes: the credential data is read and copied into separate,
|
||||
read-only copies for the unit that are accessible to appropriately privileged processes. This is
|
||||
@ -2891,6 +2907,7 @@ StandardInputData=SWNrIHNpdHplIGRhIHVuJyBlc3NlIEtsb3BzLAp1ZmYgZWVtYWwga2xvcHAncy
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>SetCredential=</varname><replaceable>ID</replaceable>:<replaceable>VALUE</replaceable></term>
|
||||
<term><varname>SetCredentialEncrypted=</varname><replaceable>ID</replaceable>:<replaceable>VALUE</replaceable></term>
|
||||
|
||||
<listitem><para>The <varname>SetCredential=</varname> setting is similar to
|
||||
<varname>LoadCredential=</varname> but accepts a literal value to use as data for the credential,
|
||||
@ -2901,6 +2918,14 @@ StandardInputData=SWNrIHNpdHplIGRhIHVuJyBlc3NlIEtsb3BzLAp1ZmYgZWVtYWwga2xvcHAncy
|
||||
C-style escaping (i.e. <literal>\n</literal> to embed a newline, or <literal>\x00</literal> to embed
|
||||
a <constant>NUL</constant> byte).</para>
|
||||
|
||||
<para>The <varname>SetCredentialEncrypted=</varname> setting is identical to
|
||||
<varname>SetCredential=</varname> but expects an encrypted credential in literal form as value. This
|
||||
allows embedding confidential credentials securely directly in unit files. Use
|
||||
<citerefentry><refentrytitle>systemd-creds</refentrytitle><manvolnum>1</manvolnum></citerefentry>'
|
||||
<option>-p</option> switch to generate suitable <varname>SetCredentialEncrypted=</varname> lines
|
||||
directly from plaintext credentials. For further details see
|
||||
<varname>LoadCredentialEncrypted=</varname> above.</para>
|
||||
|
||||
<para>If a credential of the same ID is listed in both <varname>LoadCredential=</varname> and
|
||||
<varname>SetCredential=</varname>, the latter will act as default if the former cannot be
|
||||
retrieved. In this case not being able to retrieve the credential from the path specified in
|
||||
|
14
meson.build
14
meson.build
@ -1707,7 +1707,8 @@ install_libsystemd_static = static_library(
|
||||
libcap,
|
||||
libblkid,
|
||||
libmount,
|
||||
libgcrypt],
|
||||
libgcrypt,
|
||||
libopenssl],
|
||||
c_args : libsystemd_c_args + (static_libsystemd_pic ? [] : ['-fno-PIC']))
|
||||
|
||||
libudev = shared_library(
|
||||
@ -2953,6 +2954,17 @@ public_programs += executable(
|
||||
install : true,
|
||||
install_dir : rootbindir)
|
||||
|
||||
public_programs += executable(
|
||||
'systemd-creds',
|
||||
'src/creds/creds.c',
|
||||
include_directories : includes,
|
||||
link_with : [libshared],
|
||||
dependencies : [threads,
|
||||
libopenssl],
|
||||
install_rpath : rootlibexecdir,
|
||||
install : true,
|
||||
install_dir : rootbindir)
|
||||
|
||||
executable(
|
||||
'systemd-volatile-root',
|
||||
'src/volatile-root/volatile-root.c',
|
||||
|
@ -7,10 +7,19 @@
|
||||
#include <linux/fs.h>
|
||||
|
||||
#include "chattr-util.h"
|
||||
#include "errno-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "macro.h"
|
||||
#include "string-util.h"
|
||||
|
||||
int chattr_full(const char *path,
|
||||
int fd,
|
||||
unsigned value,
|
||||
unsigned mask,
|
||||
unsigned *ret_previous,
|
||||
unsigned *ret_final,
|
||||
ChattrApplyFlags flags) {
|
||||
|
||||
int chattr_full(const char *path, int fd, unsigned value, unsigned mask, unsigned *ret_previous, unsigned *ret_final, bool fallback) {
|
||||
_cleanup_close_ int fd_will_close = -1;
|
||||
unsigned old_attr, new_attr;
|
||||
struct stat st;
|
||||
@ -57,12 +66,16 @@ int chattr_full(const char *path, int fd, unsigned value, unsigned mask, unsigne
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (errno != EINVAL || !fallback)
|
||||
if ((errno != EINVAL && !ERRNO_IS_NOT_SUPPORTED(errno)) ||
|
||||
!FLAGS_SET(flags, CHATTR_FALLBACK_BITWISE))
|
||||
return -errno;
|
||||
|
||||
/* When -EINVAL is returned, we assume that incompatible attributes are simultaneously
|
||||
* specified. E.g., compress(c) and nocow(C) attributes cannot be set to files on btrfs.
|
||||
* As a fallback, let's try to set attributes one by one. */
|
||||
* As a fallback, let's try to set attributes one by one.
|
||||
*
|
||||
* Also, when we get EOPNOTSUPP (or a similar error code) we assume a flag might just not be
|
||||
* supported, and we can ignore it too */
|
||||
|
||||
unsigned current_attr = old_attr;
|
||||
for (unsigned i = 0; i < sizeof(unsigned) * 8; i++) {
|
||||
@ -76,8 +89,12 @@ int chattr_full(const char *path, int fd, unsigned value, unsigned mask, unsigne
|
||||
continue;
|
||||
|
||||
if (ioctl(fd, FS_IOC_SETFLAGS, &new_one) < 0) {
|
||||
if (errno != EINVAL)
|
||||
if (errno != EINVAL && !ERRNO_IS_NOT_SUPPORTED(errno))
|
||||
return -errno;
|
||||
|
||||
log_full_errno(FLAGS_SET(flags, CHATTR_WARN_UNSUPPORTED_FLAGS) ? LOG_WARNING : LOG_DEBUG,
|
||||
errno,
|
||||
"Unable to set file attribute 0x%x on %s, ignoring: %m", mask_one, strna(path));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -34,13 +34,28 @@
|
||||
FS_NOCOW_FL | \
|
||||
FS_PROJINHERIT_FL)
|
||||
|
||||
int chattr_full(const char *path, int fd, unsigned value, unsigned mask, unsigned *ret_previous, unsigned *ret_final, bool fallback);
|
||||
typedef enum ChattrApplyFlags {
|
||||
CHATTR_FALLBACK_BITWISE = 1 << 0,
|
||||
CHATTR_WARN_UNSUPPORTED_FLAGS = 1 << 1,
|
||||
} ChattrApplyFlags;
|
||||
|
||||
int chattr_full(const char *path, int fd, unsigned value, unsigned mask, unsigned *ret_previous, unsigned *ret_final, ChattrApplyFlags flags);
|
||||
|
||||
static inline int chattr_fd(int fd, unsigned value, unsigned mask, unsigned *previous) {
|
||||
return chattr_full(NULL, fd, value, mask, previous, NULL, false);
|
||||
return chattr_full(NULL, fd, value, mask, previous, NULL, 0);
|
||||
}
|
||||
static inline int chattr_path(const char *path, unsigned value, unsigned mask, unsigned *previous) {
|
||||
return chattr_full(path, -1, value, mask, previous, NULL, false);
|
||||
return chattr_full(path, -1, value, mask, previous, NULL, 0);
|
||||
}
|
||||
|
||||
int read_attr_fd(int fd, unsigned *ret);
|
||||
int read_attr_path(const char *p, unsigned *ret);
|
||||
|
||||
/* Combination of chattr flags, that should be appropriate for secrets stored on disk: Secure Remove +
|
||||
* Exclusion from Dumping + Synchronous Writing (i.e. not caching in memory) + In-Place Updating (i.e. not
|
||||
* spurious copies). */
|
||||
#define CHATTR_SECRET_FLAGS (FS_SECRM_FL|FS_NODUMP_FL|FS_SYNC_FL|FS_NOCOW_FL)
|
||||
|
||||
static inline int chattr_secret(int fd, ChattrApplyFlags flags) {
|
||||
return chattr_full(NULL, fd, CHATTR_SECRET_FLAGS, CHATTR_SECRET_FLAGS, NULL, NULL, flags|CHATTR_FALLBACK_BITWISE);
|
||||
}
|
||||
|
@ -1,54 +0,0 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "creds-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "path-util.h"
|
||||
|
||||
bool credential_name_valid(const char *s) {
|
||||
/* We want that credential names are both valid in filenames (since that's our primary way to pass
|
||||
* them around) and as fdnames (which is how we might want to pass them around eventually) */
|
||||
return filename_is_valid(s) && fdname_is_valid(s);
|
||||
}
|
||||
|
||||
int get_credentials_dir(const char **ret) {
|
||||
const char *e;
|
||||
|
||||
assert(ret);
|
||||
|
||||
e = secure_getenv("CREDENTIALS_DIRECTORY");
|
||||
if (!e)
|
||||
return -ENXIO;
|
||||
|
||||
if (!path_is_absolute(e) || !path_is_normalized(e))
|
||||
return -EINVAL;
|
||||
|
||||
*ret = e;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int read_credential(const char *name, void **ret, size_t *ret_size) {
|
||||
_cleanup_free_ char *fn = NULL;
|
||||
const char *d;
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
if (!credential_name_valid(name))
|
||||
return -EINVAL;
|
||||
|
||||
r = get_credentials_dir(&d);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
fn = path_join(d, name);
|
||||
if (!fn)
|
||||
return -ENOMEM;
|
||||
|
||||
return read_full_file_full(
|
||||
AT_FDCWD, fn,
|
||||
UINT64_MAX, SIZE_MAX,
|
||||
READ_FULL_FILE_SECURE,
|
||||
NULL,
|
||||
(char**) ret, ret_size);
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "fd-util.h"
|
||||
|
||||
#define CREDENTIAL_NAME_MAX FDNAME_MAX
|
||||
|
||||
bool credential_name_valid(const char *s);
|
||||
|
||||
int get_credentials_dir(const char **ret);
|
||||
|
||||
int read_credential(const char *name, void **ret, size_t *ret_size);
|
@ -522,18 +522,17 @@ int read_full_stream_full(
|
||||
size_t *ret_size) {
|
||||
|
||||
_cleanup_free_ char *buf = NULL;
|
||||
size_t n, n_next, l;
|
||||
size_t n, n_next = 0, l;
|
||||
int fd, r;
|
||||
|
||||
assert(f);
|
||||
assert(ret_contents);
|
||||
assert(!FLAGS_SET(flags, READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX));
|
||||
assert(size != SIZE_MAX || !FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER));
|
||||
|
||||
if (offset != UINT64_MAX && offset > LONG_MAX)
|
||||
if (offset != UINT64_MAX && offset > LONG_MAX) /* fseek() can only deal with "long" offsets */
|
||||
return -ERANGE;
|
||||
|
||||
n_next = size != SIZE_MAX ? size : LINE_MAX; /* Start size */
|
||||
|
||||
fd = fileno(f);
|
||||
if (fd >= 0) { /* If the FILE* object is backed by an fd (as opposed to memory or such, see
|
||||
* fmemopen()), let's optimize our buffering */
|
||||
@ -543,20 +542,20 @@ int read_full_stream_full(
|
||||
return -errno;
|
||||
|
||||
if (S_ISREG(st.st_mode)) {
|
||||
if (size == SIZE_MAX) {
|
||||
|
||||
/* Try to start with the right file size if we shall read the file in full. Note
|
||||
* that we increase the size to read here by one, so that the first read attempt
|
||||
* already makes us notice the EOF. If the reported size of the file is zero, we
|
||||
* avoid this logic however, since quite likely it might be a virtual file in procfs
|
||||
* that all report a zero file size. */
|
||||
|
||||
if (st.st_size > 0 &&
|
||||
(size == SIZE_MAX || FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER))) {
|
||||
|
||||
uint64_t rsize =
|
||||
LESS_BY((uint64_t) st.st_size, offset == UINT64_MAX ? 0 : offset);
|
||||
|
||||
/* Safety check */
|
||||
if (rsize > READ_FULL_BYTES_MAX)
|
||||
return -E2BIG;
|
||||
|
||||
/* Start with the right file size. Note that we increase the size to read
|
||||
* here by one, so that the first read attempt already makes us notice the
|
||||
* EOF. If the reported size of the file is zero, we avoid this logic
|
||||
* however, since quite likely it might be a virtual file in procfs that all
|
||||
* report a zero file size. */
|
||||
if (st.st_size > 0)
|
||||
if (rsize < SIZE_MAX) /* overflow check */
|
||||
n_next = rsize + 1;
|
||||
}
|
||||
|
||||
@ -565,6 +564,17 @@ int read_full_stream_full(
|
||||
}
|
||||
}
|
||||
|
||||
/* If we don't know how much to read, figure it out now. If we shall read a part of the file, then
|
||||
* allocate the requested size. If we shall load the full file start with LINE_MAX. Note that if
|
||||
* READ_FULL_FILE_FAIL_WHEN_LARGER we consider the specified size a safety limit, and thus also start
|
||||
* with LINE_MAX, under assumption the file is most likely much shorter. */
|
||||
if (n_next == 0)
|
||||
n_next = size != SIZE_MAX && !FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER) ? size : LINE_MAX;
|
||||
|
||||
/* Never read more than we need to determine that our own limit is hit */
|
||||
if (n_next > READ_FULL_BYTES_MAX)
|
||||
n_next = READ_FULL_BYTES_MAX + 1;
|
||||
|
||||
if (offset != UINT64_MAX && fseek(f, offset, SEEK_SET) < 0)
|
||||
return -errno;
|
||||
|
||||
@ -573,6 +583,11 @@ int read_full_stream_full(
|
||||
char *t;
|
||||
size_t k;
|
||||
|
||||
/* If we shall fail when reading overly large data, then read exactly one byte more than the
|
||||
* specified size at max, since that'll tell us if there's anymore data beyond the limit*/
|
||||
if (FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER) && n_next > size)
|
||||
n_next = size + 1;
|
||||
|
||||
if (flags & READ_FULL_FILE_SECURE) {
|
||||
t = malloc(n_next + 1);
|
||||
if (!t) {
|
||||
@ -606,14 +621,18 @@ int read_full_stream_full(
|
||||
if (feof(f))
|
||||
break;
|
||||
|
||||
if (size != SIZE_MAX) { /* If we got asked to read some specific size, we already sized the buffer right, hence leave */
|
||||
if (size != SIZE_MAX && !FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER)) { /* If we got asked to read some specific size, we already sized the buffer right, hence leave */
|
||||
assert(l == size);
|
||||
break;
|
||||
}
|
||||
|
||||
assert(k > 0); /* we can't have read zero bytes because that would have been EOF */
|
||||
|
||||
/* Safety check */
|
||||
if (FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER) && l > size) {
|
||||
r = -E2BIG;
|
||||
goto finalize;
|
||||
}
|
||||
|
||||
if (n >= READ_FULL_BYTES_MAX) {
|
||||
r = -E2BIG;
|
||||
goto finalize;
|
||||
|
@ -39,6 +39,7 @@ typedef enum {
|
||||
READ_FULL_FILE_UNHEX = 1 << 2, /* hex decode what we read */
|
||||
READ_FULL_FILE_WARN_WORLD_READABLE = 1 << 3, /* if regular file, log at LOG_WARNING level if access mode above 0700 */
|
||||
READ_FULL_FILE_CONNECT_SOCKET = 1 << 4, /* if socket inode, connect to it and read off it */
|
||||
READ_FULL_FILE_FAIL_WHEN_LARGER = 1 << 5, /* fail loading if file is larger than specified size */
|
||||
} ReadFullFileFlags;
|
||||
|
||||
int fopen_unlocked(const char *path, const char *options, FILE **ret);
|
||||
|
@ -565,38 +565,79 @@ int unbase64char(char c) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ssize_t base64mem(const void *p, size_t l, char **out) {
|
||||
char *r, *z;
|
||||
static void maybe_line_break(char **x, char *start, size_t line_break) {
|
||||
size_t n;
|
||||
|
||||
assert(x);
|
||||
assert(*x);
|
||||
assert(start);
|
||||
assert(*x >= start);
|
||||
|
||||
if (line_break == SIZE_MAX)
|
||||
return;
|
||||
|
||||
n = *x - start;
|
||||
|
||||
if (n % (line_break + 1) == line_break)
|
||||
*((*x)++) = '\n';
|
||||
}
|
||||
|
||||
ssize_t base64mem_full(
|
||||
const void *p,
|
||||
size_t l,
|
||||
size_t line_break,
|
||||
char **out) {
|
||||
|
||||
const uint8_t *x;
|
||||
char *r, *z;
|
||||
size_t m;
|
||||
|
||||
assert(p || l == 0);
|
||||
assert(out);
|
||||
assert(line_break > 0);
|
||||
|
||||
/* three input bytes makes four output bytes, padding is added so we must round up */
|
||||
z = r = malloc(4 * (l + 2) / 3 + 1);
|
||||
m = 4 * (l + 2) / 3 + 1;
|
||||
|
||||
if (line_break != SIZE_MAX)
|
||||
m += m / line_break;
|
||||
|
||||
z = r = malloc(m);
|
||||
if (!r)
|
||||
return -ENOMEM;
|
||||
|
||||
for (x = p; x < (const uint8_t*) p + (l / 3) * 3; x += 3) {
|
||||
/* x[0] == XXXXXXXX; x[1] == YYYYYYYY; x[2] == ZZZZZZZZ */
|
||||
maybe_line_break(&z, r, line_break);
|
||||
*(z++) = base64char(x[0] >> 2); /* 00XXXXXX */
|
||||
maybe_line_break(&z, r, line_break);
|
||||
*(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */
|
||||
maybe_line_break(&z, r, line_break);
|
||||
*(z++) = base64char((x[1] & 15) << 2 | x[2] >> 6); /* 00YYYYZZ */
|
||||
maybe_line_break(&z, r, line_break);
|
||||
*(z++) = base64char(x[2] & 63); /* 00ZZZZZZ */
|
||||
}
|
||||
|
||||
switch (l % 3) {
|
||||
case 2:
|
||||
maybe_line_break(&z, r, line_break);
|
||||
*(z++) = base64char(x[0] >> 2); /* 00XXXXXX */
|
||||
maybe_line_break(&z, r, line_break);
|
||||
*(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */
|
||||
maybe_line_break(&z, r, line_break);
|
||||
*(z++) = base64char((x[1] & 15) << 2); /* 00YYYY00 */
|
||||
maybe_line_break(&z, r, line_break);
|
||||
*(z++) = '=';
|
||||
|
||||
break;
|
||||
case 1:
|
||||
maybe_line_break(&z, r, line_break);
|
||||
*(z++) = base64char(x[0] >> 2); /* 00XXXXXX */
|
||||
maybe_line_break(&z, r, line_break);
|
||||
*(z++) = base64char((x[0] & 3) << 4); /* 00XX0000 */
|
||||
maybe_line_break(&z, r, line_break);
|
||||
*(z++) = '=';
|
||||
maybe_line_break(&z, r, line_break);
|
||||
*(z++) = '=';
|
||||
|
||||
break;
|
||||
|
@ -33,7 +33,11 @@ int unbase64char(char c) _const_;
|
||||
char *base32hexmem(const void *p, size_t l, bool padding);
|
||||
int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *len);
|
||||
|
||||
ssize_t base64mem(const void *p, size_t l, char **out);
|
||||
ssize_t base64mem_full(const void *p, size_t l, size_t line_break, char **ret);
|
||||
static inline ssize_t base64mem(const void *p, size_t l, char **ret) {
|
||||
return base64mem_full(p, l, SIZE_MAX, ret);
|
||||
}
|
||||
|
||||
int base64_append(char **prefix, int plen,
|
||||
const void *p, size_t l,
|
||||
int margin, int width);
|
||||
|
@ -29,8 +29,6 @@ basic_sources = files('''
|
||||
chattr-util.h
|
||||
conf-files.c
|
||||
conf-files.h
|
||||
creds-util.c
|
||||
creds-util.h
|
||||
def.h
|
||||
dirent-util.c
|
||||
dirent-util.h
|
||||
|
@ -820,6 +820,9 @@ static int property_get_set_credential(
|
||||
|
||||
HASHMAP_FOREACH(sc, c->set_credentials) {
|
||||
|
||||
if (sc->encrypted != streq(property, "SetCredentialEncrypted"))
|
||||
continue;
|
||||
|
||||
r = sd_bus_message_open_container(reply, 'r', "say");
|
||||
if (r < 0)
|
||||
return r;
|
||||
@ -850,7 +853,7 @@ static int property_get_load_credential(
|
||||
sd_bus_error *error) {
|
||||
|
||||
ExecContext *c = userdata;
|
||||
char **i, **j;
|
||||
ExecLoadCredential *lc;
|
||||
int r;
|
||||
|
||||
assert(bus);
|
||||
@ -862,8 +865,12 @@ static int property_get_load_credential(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
STRV_FOREACH_PAIR(i, j, c->load_credentials) {
|
||||
r = sd_bus_message_append(reply, "(ss)", *i, *j);
|
||||
HASHMAP_FOREACH(lc, c->load_credentials) {
|
||||
|
||||
if (lc->encrypted != streq(property, "LoadCredentialEncrypted"))
|
||||
continue;
|
||||
|
||||
r = sd_bus_message_append(reply, "(ss)", lc->id, lc->path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
@ -1144,7 +1151,9 @@ const sd_bus_vtable bus_exec_vtable[] = {
|
||||
SD_BUS_PROPERTY("DynamicUser", "b", bus_property_get_bool, offsetof(ExecContext, dynamic_user), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("RemoveIPC", "b", bus_property_get_bool, offsetof(ExecContext, remove_ipc), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("SetCredential", "a(say)", property_get_set_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("SetCredentialEncrypted", "a(say)", property_get_set_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("LoadCredential", "a(ss)", property_get_load_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("LoadCredentialEncrypted", "a(ss)", property_get_load_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("SupplementaryGroups", "as", NULL, offsetof(ExecContext, supplementary_groups), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("PAMName", "s", NULL, offsetof(ExecContext, pam_name), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("ReadWritePaths", "as", NULL, offsetof(ExecContext, read_write_paths), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
@ -1916,7 +1925,7 @@ int bus_exec_context_set_transient_property(
|
||||
|
||||
return 1;
|
||||
|
||||
} else if (streq(name, "SetCredential")) {
|
||||
} else if (STR_IN_SET(name, "SetCredential", "SetCredentialEncrypted")) {
|
||||
bool isempty = true;
|
||||
|
||||
r = sd_bus_message_enter_container(message, 'a', "(say)");
|
||||
@ -1964,20 +1973,24 @@ int bus_exec_context_set_transient_property(
|
||||
if (old) {
|
||||
free_and_replace(old->data, copy);
|
||||
old->size = sz;
|
||||
old->encrypted = streq(name, "SetCredentialEncrypted");
|
||||
} else {
|
||||
_cleanup_(exec_set_credential_freep) ExecSetCredential *sc = NULL;
|
||||
|
||||
sc = new0(ExecSetCredential, 1);
|
||||
sc = new(ExecSetCredential, 1);
|
||||
if (!sc)
|
||||
return -ENOMEM;
|
||||
|
||||
sc->id = strdup(id);
|
||||
*sc = (ExecSetCredential) {
|
||||
.id = strdup(id),
|
||||
.data = TAKE_PTR(copy),
|
||||
.size = sz,
|
||||
.encrypted = streq(name, "SetCredentialEncrypted"),
|
||||
};
|
||||
|
||||
if (!sc->id)
|
||||
return -ENOMEM;
|
||||
|
||||
sc->data = TAKE_PTR(copy);
|
||||
sc->size = sz;
|
||||
|
||||
r = hashmap_ensure_put(&c->set_credentials, &exec_set_credential_hash_ops, sc->id, sc);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@ -2008,7 +2021,7 @@ int bus_exec_context_set_transient_property(
|
||||
|
||||
return 1;
|
||||
|
||||
} else if (streq(name, "LoadCredential")) {
|
||||
} else if (STR_IN_SET(name, "LoadCredential", "LoadCredentialEncrypted")) {
|
||||
bool isempty = true;
|
||||
|
||||
r = sd_bus_message_enter_container(message, 'a', "(ss)");
|
||||
@ -2033,9 +2046,39 @@ int bus_exec_context_set_transient_property(
|
||||
isempty = false;
|
||||
|
||||
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
|
||||
r = strv_extend_strv(&c->load_credentials, STRV_MAKE(id, source), /* filter_duplicates = */ false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
_cleanup_free_ char *copy = NULL;
|
||||
ExecLoadCredential *old;
|
||||
|
||||
copy = strdup(source);
|
||||
if (!copy)
|
||||
return -ENOMEM;
|
||||
|
||||
old = hashmap_get(c->load_credentials, id);
|
||||
if (old) {
|
||||
free_and_replace(old->path, copy);
|
||||
old->encrypted = streq(name, "LoadCredentialEncrypted");
|
||||
} else {
|
||||
_cleanup_(exec_load_credential_freep) ExecLoadCredential *lc = NULL;
|
||||
|
||||
lc = new(ExecLoadCredential, 1);
|
||||
if (!lc)
|
||||
return -ENOMEM;
|
||||
|
||||
*lc = (ExecLoadCredential) {
|
||||
.id = strdup(id),
|
||||
.path = TAKE_PTR(copy),
|
||||
.encrypted = streq(name, "LoadCredentialEncrypted"),
|
||||
};
|
||||
|
||||
if (!lc->id)
|
||||
return -ENOMEM;
|
||||
|
||||
r = hashmap_ensure_put(&c->load_credentials, &exec_load_credential_hash_ops, lc->id, lc);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
TAKE_PTR(lc);
|
||||
}
|
||||
|
||||
(void) unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "%s=%s:%s", name, id, source);
|
||||
}
|
||||
@ -2046,7 +2089,7 @@ int bus_exec_context_set_transient_property(
|
||||
return r;
|
||||
|
||||
if (!UNIT_WRITE_FLAGS_NOOP(flags) && isempty) {
|
||||
c->load_credentials = strv_free(c->load_credentials);
|
||||
c->load_credentials = hashmap_free(c->load_credentials);
|
||||
(void) unit_write_settingf(u, flags, name, "%s=", name);
|
||||
}
|
||||
|
||||
|
@ -46,6 +46,7 @@
|
||||
#include "cgroup-setup.h"
|
||||
#include "chown-recursive.h"
|
||||
#include "cpu-set-util.h"
|
||||
#include "creds-util.h"
|
||||
#include "data-fd-util.h"
|
||||
#include "def.h"
|
||||
#include "env-file.h"
|
||||
@ -1454,7 +1455,7 @@ static bool exec_context_has_credentials(const ExecContext *context) {
|
||||
assert(context);
|
||||
|
||||
return !hashmap_isempty(context->set_credentials) ||
|
||||
context->load_credentials;
|
||||
!hashmap_isempty(context->load_credentials);
|
||||
}
|
||||
|
||||
#if HAVE_SECCOMP
|
||||
@ -2486,7 +2487,7 @@ static int write_credential(
|
||||
return -errno;
|
||||
}
|
||||
|
||||
r = loop_write(fd, data, size, /* do_pool = */ false);
|
||||
r = loop_write(fd, data, size, /* do_poll = */ false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -2519,8 +2520,6 @@ static int write_credential(
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define CREDENTIALS_BYTES_MAX (1024LU * 1024LU) /* Refuse to pass more than 1M, after all this is unswappable memory */
|
||||
|
||||
static int acquire_credentials(
|
||||
const ExecContext *context,
|
||||
const ExecParameters *params,
|
||||
@ -2529,10 +2528,10 @@ static int acquire_credentials(
|
||||
uid_t uid,
|
||||
bool ownership_ok) {
|
||||
|
||||
uint64_t left = CREDENTIALS_BYTES_MAX;
|
||||
uint64_t left = CREDENTIALS_TOTAL_SIZE_MAX;
|
||||
_cleanup_close_ int dfd = -1;
|
||||
ExecLoadCredential *lc;
|
||||
ExecSetCredential *sc;
|
||||
char **id, **fn;
|
||||
int r;
|
||||
|
||||
assert(context);
|
||||
@ -2542,39 +2541,23 @@ static int acquire_credentials(
|
||||
if (dfd < 0)
|
||||
return -errno;
|
||||
|
||||
/* First we use the literally specified credentials. Note that they might be overridden again below,
|
||||
* and thus act as a "default" if the same credential is specified multiple times */
|
||||
HASHMAP_FOREACH(sc, context->set_credentials) {
|
||||
size_t add;
|
||||
|
||||
add = strlen(sc->id) + sc->size;
|
||||
if (add > left)
|
||||
return -E2BIG;
|
||||
|
||||
r = write_credential(dfd, sc->id, sc->data, sc->size, uid, ownership_ok);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
left -= add;
|
||||
}
|
||||
|
||||
/* Then, load credential off disk (or acquire via AF_UNIX socket) */
|
||||
STRV_FOREACH_PAIR(id, fn, context->load_credentials) {
|
||||
ReadFullFileFlags flags = READ_FULL_FILE_SECURE;
|
||||
/* First, load credentials off disk (or acquire via AF_UNIX socket) */
|
||||
HASHMAP_FOREACH(lc, context->load_credentials) {
|
||||
ReadFullFileFlags flags = READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER;
|
||||
_cleanup_(erase_and_freep) char *data = NULL;
|
||||
_cleanup_free_ char *j = NULL, *bindname = NULL;
|
||||
bool missing_ok = true;
|
||||
const char *source;
|
||||
size_t size, add;
|
||||
|
||||
if (path_is_absolute(*fn)) {
|
||||
if (path_is_absolute(lc->path)) {
|
||||
/* If this is an absolute path, read the data directly from it, and support AF_UNIX sockets */
|
||||
source = *fn;
|
||||
source = lc->path;
|
||||
flags |= READ_FULL_FILE_CONNECT_SOCKET;
|
||||
|
||||
/* Pass some minimal info about the unit and the credential name we are looking to acquire
|
||||
* via the source socket address in case we read off an AF_UNIX socket. */
|
||||
if (asprintf(&bindname, "@%" PRIx64"/unit/%s/%s", random_u64(), unit, *id) < 0)
|
||||
if (asprintf(&bindname, "@%" PRIx64"/unit/%s/%s", random_u64(), unit, lc->id) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
missing_ok = false;
|
||||
@ -2583,7 +2566,7 @@ static int acquire_credentials(
|
||||
/* If this is a relative path, take it relative to the credentials we received
|
||||
* ourselves. We don't support the AF_UNIX stuff in this mode, since we are operating
|
||||
* on a credential store, i.e. this is guaranteed to be regular files. */
|
||||
j = path_join(params->received_credentials, *fn);
|
||||
j = path_join(params->received_credentials, lc->path);
|
||||
if (!j)
|
||||
return -ENOMEM;
|
||||
|
||||
@ -2592,34 +2575,87 @@ static int acquire_credentials(
|
||||
source = NULL;
|
||||
|
||||
if (source)
|
||||
r = read_full_file_full(AT_FDCWD, source, UINT64_MAX, SIZE_MAX, flags, bindname, &data, &size);
|
||||
r = read_full_file_full(
|
||||
AT_FDCWD, source,
|
||||
UINT64_MAX,
|
||||
lc->encrypted ? CREDENTIAL_ENCRYPTED_SIZE_MAX : CREDENTIAL_SIZE_MAX,
|
||||
flags | (lc->encrypted ? READ_FULL_FILE_UNBASE64 : 0),
|
||||
bindname,
|
||||
&data, &size);
|
||||
else
|
||||
r = -ENOENT;
|
||||
if (r == -ENOENT && (missing_ok || faccessat(dfd, *id, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)) {
|
||||
if (r == -ENOENT && (missing_ok || hashmap_contains(context->set_credentials, lc->id))) {
|
||||
/* Make a missing inherited credential non-fatal, let's just continue. After all apps
|
||||
* will get clear errors if we don't pass such a missing credential on as they
|
||||
* themselves will get ENOENT when trying to read them, which should not be much
|
||||
* worse than when we handle the error here and make it fatal.
|
||||
*
|
||||
* Also, if the source file doesn't exist, but we already acquired the key otherwise,
|
||||
* then don't fail either. */
|
||||
log_debug_errno(r, "Couldn't read inherited credential '%s', skipping: %m", *fn);
|
||||
* Also, if the source file doesn't exist, but a fallback is set via SetCredentials=
|
||||
* we are fine, too. */
|
||||
log_debug_errno(r, "Couldn't read inherited credential '%s', skipping: %m", lc->path);
|
||||
continue;
|
||||
}
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to read credential '%s': %m", *fn);
|
||||
return log_debug_errno(r, "Failed to read credential '%s': %m", lc->path);
|
||||
|
||||
add = strlen(*id) + size;
|
||||
if (lc->encrypted) {
|
||||
_cleanup_free_ void *plaintext = NULL;
|
||||
size_t plaintext_size = 0;
|
||||
|
||||
r = decrypt_credential_and_warn(lc->id, now(CLOCK_REALTIME), NULL, data, size, &plaintext, &plaintext_size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
free_and_replace(data, plaintext);
|
||||
size = plaintext_size;
|
||||
}
|
||||
|
||||
add = strlen(lc->id) + size;
|
||||
if (add > left)
|
||||
return -E2BIG;
|
||||
|
||||
r = write_credential(dfd, *id, data, size, uid, ownership_ok);
|
||||
r = write_credential(dfd, lc->id, data, size, uid, ownership_ok);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
left -= add;
|
||||
}
|
||||
|
||||
/* First we use the literally specified credentials. Note that they might be overridden again below,
|
||||
* and thus act as a "default" if the same credential is specified multiple times */
|
||||
HASHMAP_FOREACH(sc, context->set_credentials) {
|
||||
_cleanup_(erase_and_freep) void *plaintext = NULL;
|
||||
const char *data;
|
||||
size_t size, add;
|
||||
|
||||
if (faccessat(dfd, sc->id, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
|
||||
continue;
|
||||
if (errno != ENOENT)
|
||||
return log_debug_errno(errno, "Failed to test if credential %s exists: %m", sc->id);
|
||||
|
||||
if (sc->encrypted) {
|
||||
r = decrypt_credential_and_warn(sc->id, now(CLOCK_REALTIME), NULL, sc->data, sc->size, &plaintext, &size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
data = plaintext;
|
||||
} else {
|
||||
data = sc->data;
|
||||
size = sc->size;
|
||||
}
|
||||
|
||||
add = strlen(sc->id) + size;
|
||||
if (add > left)
|
||||
return -E2BIG;
|
||||
|
||||
r = write_credential(dfd, sc->id, data, size, uid, ownership_ok);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
||||
left -= add;
|
||||
}
|
||||
|
||||
if (fchmod(dfd, 0500) < 0) /* Now take away the "w" bit */
|
||||
return -errno;
|
||||
|
||||
@ -2715,7 +2751,7 @@ static int setup_credentials_internal(
|
||||
} else if (try == 1) {
|
||||
_cleanup_free_ char *opts = NULL;
|
||||
|
||||
if (asprintf(&opts, "mode=0700,nr_inodes=1024,size=%lu", CREDENTIALS_BYTES_MAX) < 0)
|
||||
if (asprintf(&opts, "mode=0700,nr_inodes=1024,size=%zu", (size_t) CREDENTIALS_TOTAL_SIZE_MAX) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Fall back to "tmpfs" otherwise */
|
||||
@ -4928,7 +4964,7 @@ void exec_context_done(ExecContext *c) {
|
||||
|
||||
c->log_namespace = mfree(c->log_namespace);
|
||||
|
||||
c->load_credentials = strv_free(c->load_credentials);
|
||||
c->load_credentials = hashmap_free(c->load_credentials);
|
||||
c->set_credentials = hashmap_free(c->set_credentials);
|
||||
}
|
||||
|
||||
@ -6597,7 +6633,17 @@ ExecSetCredential *exec_set_credential_free(ExecSetCredential *sc) {
|
||||
return mfree(sc);
|
||||
}
|
||||
|
||||
ExecLoadCredential *exec_load_credential_free(ExecLoadCredential *lc) {
|
||||
if (!lc)
|
||||
return NULL;
|
||||
|
||||
free(lc->id);
|
||||
free(lc->path);
|
||||
return mfree(lc);
|
||||
}
|
||||
|
||||
DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(exec_set_credential_hash_ops, char, string_hash_func, string_compare_func, ExecSetCredential, exec_set_credential_free);
|
||||
DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(exec_load_credential_hash_ops, char, string_hash_func, string_compare_func, ExecLoadCredential, exec_load_credential_free);
|
||||
|
||||
static const char* const exec_input_table[_EXEC_INPUT_MAX] = {
|
||||
[EXEC_INPUT_NULL] = "null",
|
||||
|
@ -150,9 +150,16 @@ typedef enum ExecCleanMask {
|
||||
_EXEC_CLEAN_MASK_INVALID = -EINVAL,
|
||||
} ExecCleanMask;
|
||||
|
||||
/* A credential configured with LoadCredential= */
|
||||
typedef struct ExecLoadCredential {
|
||||
char *id, *path;
|
||||
bool encrypted;
|
||||
} ExecLoadCredential;
|
||||
|
||||
/* A credential configured with SetCredential= */
|
||||
typedef struct ExecSetCredential {
|
||||
char *id;
|
||||
bool encrypted;
|
||||
void *data;
|
||||
size_t size;
|
||||
} ExecSetCredential;
|
||||
@ -325,7 +332,7 @@ struct ExecContext {
|
||||
usec_t timeout_clean_usec;
|
||||
|
||||
Hashmap *set_credentials; /* output id → ExecSetCredential */
|
||||
char **load_credentials; /* pairs of output id, path/input id */
|
||||
Hashmap *load_credentials; /* output id → ExecLoadCredential */
|
||||
};
|
||||
|
||||
static inline bool exec_context_restrict_namespaces_set(const ExecContext *c) {
|
||||
@ -458,7 +465,11 @@ bool exec_context_get_cpu_affinity_from_numa(const ExecContext *c);
|
||||
ExecSetCredential *exec_set_credential_free(ExecSetCredential *sc);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(ExecSetCredential*, exec_set_credential_free);
|
||||
|
||||
ExecLoadCredential *exec_load_credential_free(ExecLoadCredential *lc);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(ExecLoadCredential*, exec_load_credential_free);
|
||||
|
||||
extern const struct hash_ops exec_set_credential_hash_ops;
|
||||
extern const struct hash_ops exec_load_credential_hash_ops;
|
||||
|
||||
const char* exec_output_to_string(ExecOutput i) _const_;
|
||||
ExecOutput exec_output_from_string(const char *s) _pure_;
|
||||
|
@ -139,7 +139,9 @@
|
||||
{{type}}.ConfigurationDirectoryMode, config_parse_mode, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_CONFIGURATION].mode)
|
||||
{{type}}.ConfigurationDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_CONFIGURATION].paths)
|
||||
{{type}}.SetCredential, config_parse_set_credential, 0, offsetof({{type}}, exec_context)
|
||||
{{type}}.SetCredentialEncrypted, config_parse_set_credential, 1, offsetof({{type}}, exec_context)
|
||||
{{type}}.LoadCredential, config_parse_load_credential, 0, offsetof({{type}}, exec_context)
|
||||
{{type}}.LoadCredentialEncrypted, config_parse_load_credential, 1, offsetof({{type}}, exec_context)
|
||||
{{type}}.TimeoutCleanSec, config_parse_sec, 0, offsetof({{type}}, exec_context.timeout_clean_usec)
|
||||
{% if HAVE_PAM %}
|
||||
{{type}}.PAMName, config_parse_unit_string_printf, 0, offsetof({{type}}, exec_context.pam_name)
|
||||
|
@ -4469,12 +4469,15 @@ int config_parse_set_credential(
|
||||
void *data,
|
||||
void *userdata) {
|
||||
|
||||
_cleanup_free_ char *word = NULL, *k = NULL, *unescaped = NULL;
|
||||
_cleanup_free_ char *word = NULL, *k = NULL;
|
||||
_cleanup_free_ void *d = NULL;
|
||||
ExecContext *context = data;
|
||||
ExecSetCredential *old;
|
||||
Unit *u = userdata;
|
||||
const char *p;
|
||||
int r, l;
|
||||
bool encrypted = ltype;
|
||||
const char *p = rvalue;
|
||||
size_t size;
|
||||
int r;
|
||||
|
||||
assert(filename);
|
||||
assert(lvalue);
|
||||
@ -4487,7 +4490,6 @@ int config_parse_set_credential(
|
||||
return 0;
|
||||
}
|
||||
|
||||
p = rvalue;
|
||||
r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
|
||||
if (r == -ENOMEM)
|
||||
return log_oom();
|
||||
@ -4506,33 +4508,51 @@ int config_parse_set_credential(
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We support escape codes here, so that users can insert trailing \n if they like */
|
||||
l = cunescape(p, UNESCAPE_ACCEPT_NUL, &unescaped);
|
||||
if (l < 0) {
|
||||
log_syntax(unit, LOG_WARNING, filename, line, l, "Can't unescape \"%s\", ignoring: %m", p);
|
||||
return 0;
|
||||
if (encrypted) {
|
||||
r = unbase64mem_full(p, SIZE_MAX, true, &d, &size);
|
||||
if (r < 0) {
|
||||
log_syntax(unit, LOG_WARNING, filename, line, r, "Encrypted credential data not valid Base64 data, ignoring.");
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
char *unescaped = NULL;
|
||||
int l;
|
||||
|
||||
/* We support escape codes here, so that users can insert trailing \n if they like */
|
||||
l = cunescape(p, UNESCAPE_ACCEPT_NUL, &unescaped);
|
||||
if (l < 0) {
|
||||
log_syntax(unit, LOG_WARNING, filename, line, l, "Can't unescape \"%s\", ignoring: %m", p);
|
||||
return 0;
|
||||
}
|
||||
|
||||
d = unescaped;
|
||||
size = l;
|
||||
}
|
||||
|
||||
old = hashmap_get(context->set_credentials, k);
|
||||
if (old) {
|
||||
free_and_replace(old->data, unescaped);
|
||||
old->size = l;
|
||||
free_and_replace(old->data, d);
|
||||
old->size = size;
|
||||
old->encrypted = encrypted;
|
||||
} else {
|
||||
_cleanup_(exec_set_credential_freep) ExecSetCredential *sc = NULL;
|
||||
|
||||
sc = new0(ExecSetCredential, 1);
|
||||
sc = new(ExecSetCredential, 1);
|
||||
if (!sc)
|
||||
return log_oom();
|
||||
|
||||
sc->id = TAKE_PTR(k);
|
||||
sc->data = TAKE_PTR(unescaped);
|
||||
sc->size = l;
|
||||
*sc = (ExecSetCredential) {
|
||||
.id = TAKE_PTR(k),
|
||||
.data = TAKE_PTR(d),
|
||||
.size = size,
|
||||
.encrypted = encrypted,
|
||||
};
|
||||
|
||||
r = hashmap_ensure_put(&context->set_credentials, &exec_set_credential_hash_ops, sc->id, sc);
|
||||
if (r == -ENOMEM)
|
||||
return log_oom();
|
||||
if (r < 0) {
|
||||
log_syntax(unit, LOG_WARNING, filename, line, l,
|
||||
log_syntax(unit, LOG_WARNING, filename, line, r,
|
||||
"Duplicated credential value '%s', ignoring assignment: %s", sc->id, rvalue);
|
||||
return 0;
|
||||
}
|
||||
@ -4557,6 +4577,8 @@ int config_parse_load_credential(
|
||||
|
||||
_cleanup_free_ char *word = NULL, *k = NULL, *q = NULL;
|
||||
ExecContext *context = data;
|
||||
ExecLoadCredential *old;
|
||||
bool encrypted = ltype;
|
||||
Unit *u = userdata;
|
||||
const char *p;
|
||||
int r;
|
||||
@ -4568,7 +4590,7 @@ int config_parse_load_credential(
|
||||
|
||||
if (isempty(rvalue)) {
|
||||
/* Empty assignment resets the list */
|
||||
context->load_credentials = strv_free(context->load_credentials);
|
||||
context->load_credentials = hashmap_free(context->load_credentials);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -4604,14 +4626,39 @@ int config_parse_load_credential(
|
||||
return 0;
|
||||
}
|
||||
if (path_is_absolute(q) ? !path_is_normalized(q) : !credential_name_valid(q)) {
|
||||
log_syntax(unit, LOG_WARNING, filename, line, r, "Credential source \"%s\" not valid, ignoring.", q);
|
||||
log_syntax(unit, LOG_WARNING, filename, line, 0, "Credential source \"%s\" not valid, ignoring.", q);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
r = strv_consume_pair(&context->load_credentials, TAKE_PTR(k), TAKE_PTR(q));
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
old = hashmap_get(context->load_credentials, k);
|
||||
if (old) {
|
||||
free_and_replace(old->path, q);
|
||||
old->encrypted = encrypted;
|
||||
} else {
|
||||
_cleanup_(exec_load_credential_freep) ExecLoadCredential *lc = NULL;
|
||||
|
||||
lc = new(ExecLoadCredential, 1);
|
||||
if (!lc)
|
||||
return log_oom();
|
||||
|
||||
*lc = (ExecLoadCredential) {
|
||||
.id = TAKE_PTR(k),
|
||||
.path = TAKE_PTR(q),
|
||||
.encrypted = encrypted,
|
||||
};
|
||||
|
||||
r = hashmap_ensure_put(&context->load_credentials, &exec_load_credential_hash_ops, lc->id, lc);
|
||||
if (r == -ENOMEM)
|
||||
return log_oom();
|
||||
if (r < 0) {
|
||||
log_syntax(unit, LOG_WARNING, filename, line, r,
|
||||
"Duplicated credential value '%s', ignoring assignment: %s", lc->id, rvalue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
TAKE_PTR(lc);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
812
src/creds/creds.c
Normal file
812
src/creds/creds.c
Normal file
@ -0,0 +1,812 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <getopt.h>
|
||||
|
||||
#include "creds-util.h"
|
||||
#include "dirent-util.h"
|
||||
#include "escape.h"
|
||||
#include "fileio.h"
|
||||
#include "format-table.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "io-util.h"
|
||||
#include "json.h"
|
||||
#include "main-func.h"
|
||||
#include "memory-util.h"
|
||||
#include "missing_magic.h"
|
||||
#include "pager.h"
|
||||
#include "parse-argument.h"
|
||||
#include "pretty-print.h"
|
||||
#include "process-util.h"
|
||||
#include "stat-util.h"
|
||||
#include "string-table.h"
|
||||
#include "terminal-util.h"
|
||||
#include "tpm2-util.h"
|
||||
#include "verbs.h"
|
||||
|
||||
typedef enum TranscodeMode {
|
||||
TRANSCODE_OFF,
|
||||
TRANSCODE_BASE64,
|
||||
TRANSCODE_UNBASE64,
|
||||
TRANSCODE_HEX,
|
||||
TRANSCODE_UNHEX,
|
||||
_TRANSCODE_MAX,
|
||||
_TRANSCODE_INVALID = -EINVAL,
|
||||
} TranscodeMode;
|
||||
|
||||
static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
|
||||
static PagerFlags arg_pager_flags = 0;
|
||||
static bool arg_legend = true;
|
||||
static bool arg_system = false;
|
||||
static TranscodeMode arg_transcode = TRANSCODE_OFF;
|
||||
static int arg_newline = -1;
|
||||
static sd_id128_t arg_with_key = SD_ID128_NULL;
|
||||
static const char *arg_tpm2_device = NULL;
|
||||
static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
|
||||
static const char *arg_name = NULL;
|
||||
static bool arg_name_any = false;
|
||||
static usec_t arg_timestamp = USEC_INFINITY;
|
||||
static usec_t arg_not_after = USEC_INFINITY;
|
||||
static bool arg_pretty = false;
|
||||
|
||||
static const char* transcode_mode_table[_TRANSCODE_MAX] = {
|
||||
[TRANSCODE_OFF] = "off",
|
||||
[TRANSCODE_BASE64] = "base64",
|
||||
[TRANSCODE_UNBASE64] = "unbase64",
|
||||
[TRANSCODE_HEX] = "hex",
|
||||
[TRANSCODE_UNHEX] = "unhex",
|
||||
};
|
||||
|
||||
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(transcode_mode, TranscodeMode);
|
||||
|
||||
static int open_credential_directory(DIR **ret) {
|
||||
_cleanup_free_ char *j = NULL;
|
||||
const char *p;
|
||||
DIR *d;
|
||||
int r;
|
||||
|
||||
if (arg_system) {
|
||||
_cleanup_free_ char *cd = NULL;
|
||||
|
||||
r = getenv_for_pid(1, "CREDENTIALS_DIRECTORY", &cd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (!cd)
|
||||
return -ENXIO;
|
||||
|
||||
if (!path_is_absolute(cd) || !path_is_normalized(cd))
|
||||
return -EINVAL;
|
||||
|
||||
j = path_join("/proc/1/root", cd);
|
||||
if (!j)
|
||||
return -ENOMEM;
|
||||
|
||||
p = j;
|
||||
} else {
|
||||
r = get_credentials_dir(&p);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
d = opendir(p);
|
||||
if (!d)
|
||||
return -errno;
|
||||
|
||||
*ret = d;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int verb_list(int argc, char **argv, void *userdata) {
|
||||
_cleanup_(table_unrefp) Table *t = NULL;
|
||||
_cleanup_(closedirp) DIR *d = NULL;
|
||||
int r;
|
||||
|
||||
r = open_credential_directory(&d);
|
||||
if (r == -ENXIO)
|
||||
return log_error_errno(r, "No credentials received. (i.e. $CREDENTIALS_PATH not set or pointing to empty directory.)");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to open credentials directory: %m");
|
||||
|
||||
t = table_new("name", "secure", "size");
|
||||
if (!t)
|
||||
return log_oom();
|
||||
|
||||
(void) table_set_align_percent(t, table_get_cell(t, 0, 2), 100);
|
||||
|
||||
for (;;) {
|
||||
_cleanup_close_ int fd = -1;
|
||||
const char *secure, *secure_color = NULL;
|
||||
struct dirent *de;
|
||||
struct stat st;
|
||||
|
||||
errno = 0;
|
||||
de = readdir_no_dot(d);
|
||||
if (!de) {
|
||||
if (errno == 0)
|
||||
break;
|
||||
|
||||
return log_error_errno(errno, "Failed to read credentials directory: %m");
|
||||
}
|
||||
|
||||
if (!IN_SET(de->d_type, DT_REG, DT_UNKNOWN))
|
||||
continue;
|
||||
|
||||
if (!credential_name_valid(de->d_name))
|
||||
continue;
|
||||
|
||||
fd = openat(dirfd(d), de->d_name, O_PATH|O_CLOEXEC|O_NOFOLLOW);
|
||||
if (fd < 0) {
|
||||
if (errno == ENOENT) /* Vanished by now? */
|
||||
continue;
|
||||
|
||||
return log_error_errno(errno, "Failed to open credential '%s': %m", de->d_name);
|
||||
}
|
||||
|
||||
if (fstat(fd, &st) < 0)
|
||||
return log_error_errno(errno, "Failed to stat credential '%s': %m", de->d_name);
|
||||
|
||||
if (!S_ISREG(st.st_mode))
|
||||
continue;
|
||||
|
||||
if ((st.st_mode & 0377) != 0) {
|
||||
secure = "insecure"; /* Anything that is accessible more than read-only to its owner is insecure */
|
||||
secure_color = ansi_highlight_red();
|
||||
} else {
|
||||
r = fd_is_fs_type(fd, RAMFS_MAGIC);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to determine backing file system of '%s': %m", de->d_name);
|
||||
|
||||
secure = r ? "secure" : "weak"; /* ramfs is not swappable, hence "secure", everything else is "weak" */
|
||||
secure_color = r ? ansi_highlight_green() : ansi_highlight_yellow4();
|
||||
}
|
||||
|
||||
r = table_add_many(
|
||||
t,
|
||||
TABLE_STRING, de->d_name,
|
||||
TABLE_STRING, secure,
|
||||
TABLE_SET_COLOR, secure_color,
|
||||
TABLE_SIZE, (uint64_t) st.st_size);
|
||||
if (r < 0)
|
||||
return table_log_add_error(r);
|
||||
}
|
||||
|
||||
if ((arg_json_format_flags & JSON_FORMAT_OFF) && table_get_rows(t) <= 1) {
|
||||
log_info("No credentials");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
|
||||
}
|
||||
|
||||
static int transcode(
|
||||
const void *input,
|
||||
size_t input_size,
|
||||
void **ret_output,
|
||||
size_t *ret_output_size) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(input);
|
||||
assert(input_size);
|
||||
assert(ret_output);
|
||||
assert(ret_output_size);
|
||||
|
||||
switch (arg_transcode) {
|
||||
|
||||
case TRANSCODE_BASE64: {
|
||||
char *buf = NULL;
|
||||
ssize_t l;
|
||||
|
||||
l = base64mem_full(input, input_size, 79, &buf);
|
||||
if (l < 0)
|
||||
return l;
|
||||
|
||||
*ret_output = buf;
|
||||
*ret_output_size = l;
|
||||
return 0;
|
||||
}
|
||||
|
||||
case TRANSCODE_UNBASE64:
|
||||
r = unbase64mem_full(input, input_size, true, ret_output, ret_output_size);
|
||||
if (r == -EPIPE) /* Uneven number of chars */
|
||||
return -EINVAL;
|
||||
|
||||
return r;
|
||||
|
||||
case TRANSCODE_HEX: {
|
||||
char *buf;
|
||||
|
||||
buf = hexmem(input, input_size);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
*ret_output = buf;
|
||||
*ret_output_size = input_size * 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
case TRANSCODE_UNHEX:
|
||||
r = unhexmem_full(input, input_size, true, ret_output, ret_output_size);
|
||||
if (r == -EPIPE) /* Uneven number of chars */
|
||||
return -EINVAL;
|
||||
|
||||
return r;
|
||||
|
||||
default:
|
||||
assert_not_reached("Unexpected transcoding mode");
|
||||
}
|
||||
}
|
||||
|
||||
static int print_newline(FILE *f, const char *data, size_t l) {
|
||||
int fd;
|
||||
|
||||
assert(f);
|
||||
assert(data || l == 0);
|
||||
|
||||
/* If turned off explicitly, don't print newline */
|
||||
if (arg_newline == 0)
|
||||
return 0;
|
||||
|
||||
/* If data already has newline, don't print either */
|
||||
if (l > 0 && data[l-1] == '\n')
|
||||
return 0;
|
||||
|
||||
/* Don't bother unless this is a tty */
|
||||
fd = fileno(f);
|
||||
if (fd >= 0 && isatty(fd) <= 0)
|
||||
return 0;
|
||||
|
||||
if (fputc('\n', f) != '\n')
|
||||
return log_error_errno(errno, "Failed to write trailing newline: %m");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int write_blob(FILE *f, const void *data, size_t size) {
|
||||
_cleanup_(erase_and_freep) void *transcoded = NULL;
|
||||
int r;
|
||||
|
||||
if (arg_transcode == TRANSCODE_OFF &&
|
||||
arg_json_format_flags != JSON_FORMAT_OFF) {
|
||||
|
||||
_cleanup_(erase_and_freep) char *suffixed = NULL;
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||
|
||||
if (memchr(data, 0, size))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Credential data contains embedded NUL, can't parse as JSON.");
|
||||
|
||||
suffixed = memdup_suffix0(data, size);
|
||||
if (!suffixed)
|
||||
return log_oom();
|
||||
|
||||
r = json_parse(suffixed, JSON_PARSE_SENSITIVE, &v, NULL, NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse JSON: %m");
|
||||
|
||||
json_variant_dump(v, arg_json_format_flags, f, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (arg_transcode != TRANSCODE_OFF) {
|
||||
r = transcode(data, size, &transcoded, &size);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to transcode data: %m");
|
||||
|
||||
data = transcoded;
|
||||
}
|
||||
|
||||
if (fwrite(data, 1, size, f) != size)
|
||||
return log_error_errno(errno, "Failed to write credential data: %m");
|
||||
|
||||
r = print_newline(f, data, size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (fflush(f) != 0)
|
||||
return log_error_errno(errno, "Failed to flush output: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int verb_cat(int argc, char **argv, void *userdata) {
|
||||
_cleanup_(closedirp) DIR *d = NULL;
|
||||
int r, ret = 0;
|
||||
char **cn;
|
||||
|
||||
r = open_credential_directory(&d);
|
||||
if (r == -ENXIO)
|
||||
return log_error_errno(r, "No credentials passed.");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to open credentials directory: %m");
|
||||
|
||||
STRV_FOREACH(cn, strv_skip(argv, 1)) {
|
||||
_cleanup_(erase_and_freep) void *data = NULL;
|
||||
size_t size = 0;
|
||||
|
||||
if (!credential_name_valid(*cn)) {
|
||||
log_error("Credential name '%s' is not valid.", *cn);
|
||||
if (ret >= 0)
|
||||
ret = -EINVAL;
|
||||
continue;
|
||||
}
|
||||
|
||||
r = read_full_file_full(
|
||||
dirfd(d), *cn,
|
||||
UINT64_MAX, SIZE_MAX,
|
||||
READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE,
|
||||
NULL,
|
||||
(char**) &data, &size);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to read credential '%s': %m", *cn);
|
||||
if (ret >= 0)
|
||||
ret = r;
|
||||
continue;
|
||||
}
|
||||
|
||||
r = write_blob(stdout, data, size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int verb_encrypt(int argc, char **argv, void *userdata) {
|
||||
_cleanup_free_ char *base64_buf = NULL, *fname = NULL;
|
||||
_cleanup_(erase_and_freep) char *plaintext = NULL;
|
||||
const char *input_path, *output_path, *name;
|
||||
_cleanup_free_ void *output = NULL;
|
||||
size_t plaintext_size, output_size;
|
||||
ssize_t base64_size;
|
||||
usec_t timestamp;
|
||||
int r;
|
||||
|
||||
assert(argc == 3);
|
||||
|
||||
input_path = (isempty(argv[1]) || streq(argv[1], "-")) ? NULL : argv[1];
|
||||
|
||||
if (input_path)
|
||||
r = read_full_file_full(AT_FDCWD, input_path, UINT64_MAX, CREDENTIAL_SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &plaintext, &plaintext_size);
|
||||
else
|
||||
r = read_full_stream_full(stdin, NULL, UINT64_MAX, CREDENTIAL_SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER, &plaintext, &plaintext_size);
|
||||
if (r == -E2BIG)
|
||||
return log_error_errno(r, "Plaintext too long for credential (allowed size: %zu).", (size_t) CREDENTIAL_SIZE_MAX);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read plaintext: %m");
|
||||
|
||||
output_path = (isempty(argv[2]) || streq(argv[2], "-")) ? NULL : argv[2];
|
||||
|
||||
if (arg_name_any)
|
||||
name = NULL;
|
||||
else if (arg_name)
|
||||
name = arg_name;
|
||||
else if (output_path) {
|
||||
r = path_extract_filename(output_path, &fname);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to extract filename from '%s': %m", output_path);
|
||||
if (r == O_DIRECTORY)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Path '%s' refers to directory, refusing.", output_path);
|
||||
|
||||
name = fname;
|
||||
} else {
|
||||
log_warning("No credential name specified, not embedding credential name in encrypted data. (Disable this warning with --name=)");
|
||||
name = NULL;
|
||||
}
|
||||
|
||||
timestamp = arg_timestamp != USEC_INFINITY ? arg_timestamp : now(CLOCK_REALTIME);
|
||||
|
||||
if (arg_not_after != USEC_INFINITY && arg_not_after < timestamp)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential is invalidated before it is valid.");
|
||||
|
||||
r = encrypt_credential_and_warn(
|
||||
arg_with_key,
|
||||
name,
|
||||
timestamp,
|
||||
arg_not_after,
|
||||
arg_tpm2_device,
|
||||
arg_tpm2_pcr_mask,
|
||||
plaintext, plaintext_size,
|
||||
&output, &output_size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
base64_size = base64mem_full(output, output_size, arg_pretty ? 69 : 79, &base64_buf);
|
||||
if (base64_size < 0)
|
||||
return base64_size;
|
||||
|
||||
if (arg_pretty) {
|
||||
_cleanup_free_ char *escaped = NULL, *indented = NULL, *j = NULL;
|
||||
|
||||
if (name) {
|
||||
escaped = cescape(name);
|
||||
if (!escaped)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
indented = strreplace(base64_buf, "\n", " \\\n ");
|
||||
if (!indented)
|
||||
return log_oom();
|
||||
|
||||
j = strjoin("SetCredentialEncrypted=", name, ": \\\n ", indented, "\n");
|
||||
if (!j)
|
||||
return log_oom();
|
||||
|
||||
free_and_replace(base64_buf, j);
|
||||
}
|
||||
|
||||
if (output_path)
|
||||
r = write_string_file(output_path, base64_buf, WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_CREATE);
|
||||
else
|
||||
r = write_string_stream(stdout, base64_buf, 0);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write result: %m");
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
static int verb_decrypt(int argc, char **argv, void *userdata) {
|
||||
_cleanup_(erase_and_freep) void *plaintext = NULL;
|
||||
_cleanup_free_ char *input = NULL, *fname = NULL;
|
||||
_cleanup_fclose_ FILE *output_file = NULL;
|
||||
const char *input_path, *output_path, *name;
|
||||
size_t input_size, plaintext_size;
|
||||
usec_t timestamp;
|
||||
FILE *f;
|
||||
int r;
|
||||
|
||||
assert(IN_SET(argc, 2, 3));
|
||||
|
||||
input_path = (isempty(argv[1]) || streq(argv[1], "-")) ? NULL : argv[1];
|
||||
|
||||
if (input_path)
|
||||
r = read_full_file_full(AT_FDCWD, argv[1], UINT64_MAX, CREDENTIAL_ENCRYPTED_SIZE_MAX, READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &input, &input_size);
|
||||
else
|
||||
r = read_full_stream_full(stdin, NULL, UINT64_MAX, CREDENTIAL_ENCRYPTED_SIZE_MAX, READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER, &input, &input_size);
|
||||
if (r == -E2BIG)
|
||||
return log_error_errno(r, "Data too long for encrypted credential (allowed size: %zu).", (size_t) CREDENTIAL_ENCRYPTED_SIZE_MAX);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read encrypted credential data: %m");
|
||||
|
||||
output_path = (argc < 3 || isempty(argv[2]) || streq(argv[2], "-")) ? NULL : argv[2];
|
||||
|
||||
if (arg_name_any)
|
||||
name = NULL;
|
||||
else if (arg_name)
|
||||
name = arg_name;
|
||||
else if (input_path) {
|
||||
r = path_extract_filename(input_path, &fname);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to extract filename from '%s': %m", input_path);
|
||||
if (r == O_DIRECTORY)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Path '%s' refers to directory, refusing.", input_path);
|
||||
|
||||
name = fname;
|
||||
} else {
|
||||
log_warning("No credential name specified, not validating credential name embedded in encrypted data. (Disable this warning with --name=.)");
|
||||
name = NULL;
|
||||
}
|
||||
|
||||
timestamp = arg_timestamp != USEC_INFINITY ? arg_timestamp : now(CLOCK_REALTIME);
|
||||
|
||||
r = decrypt_credential_and_warn(
|
||||
name,
|
||||
timestamp,
|
||||
arg_tpm2_device,
|
||||
input, input_size,
|
||||
&plaintext, &plaintext_size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (output_path) {
|
||||
output_file = fopen(output_path, "we");
|
||||
if (!output_file)
|
||||
return log_error_errno(errno, "Failed to create output file '%s': %m", output_path);
|
||||
|
||||
f = output_file;
|
||||
} else
|
||||
f = stdout;
|
||||
|
||||
r = write_blob(f, plaintext, plaintext_size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
static int verb_setup(int argc, char **argv, void *userdata) {
|
||||
size_t size;
|
||||
int r;
|
||||
|
||||
r = get_credential_host_secret(CREDENTIAL_SECRET_GENERATE|CREDENTIAL_SECRET_WARN_NOT_ENCRYPTED, NULL, &size);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to setup credentials host key: %m");
|
||||
|
||||
log_info("%zu byte credentials host key set up.", size);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
static int verb_help(int argc, char **argv, void *userdata) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
int r;
|
||||
|
||||
r = terminal_urlify_man("systemd-creds", "1", &link);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
printf("%1$s [OPTIONS...] COMMAND ...\n"
|
||||
"\n%5$sDisplay and Process Credentials.%6$s\n"
|
||||
"\n%3$sCommands:%4$s\n"
|
||||
" list Show installed and available versions\n"
|
||||
" cat CREDENTIAL... Show specified credentials\n"
|
||||
" setup Generate credentials host key, if not existing yet\n"
|
||||
" encrypt INPUT OUTPUT Encrypt plaintext credential file and write to\n"
|
||||
" ciphertext credential file\n"
|
||||
" decrypt INPUT [OUTPUT] Decrypt ciphertext credential file and write to\n"
|
||||
" plaintext credential file\n"
|
||||
" -h --help Show this help\n"
|
||||
" --version Show package version\n"
|
||||
"\n%3$sOptions:%4$s\n"
|
||||
" --no-pager Do not pipe output into a pager\n"
|
||||
" --no-legend Do not show the headers and footers\n"
|
||||
" --json=pretty|short|off\n"
|
||||
" Generate JSON output\n"
|
||||
" --system Show credentials passed to system\n"
|
||||
" --transcode=base64|unbase64|hex|unhex\n"
|
||||
" Transcode credential data\n"
|
||||
" --newline=auto|yes|no\n"
|
||||
" Suffix output with newline\n"
|
||||
" -p --pretty Output as SetCredentialEncrypted= line\n"
|
||||
" --name=NAME Override filename included in encrypted credential\n"
|
||||
" --timestamp=TIME Include specified timestamp in encrypted credential\n"
|
||||
" --not-after=TIME Include specified invalidation time in encrypted\n"
|
||||
" credential\n"
|
||||
" --with-key=host|tpm2|host+tpm2|auto\n"
|
||||
" Which keys to encrypt with\n"
|
||||
" -H Shortcut for --with-key=host\n"
|
||||
" -T Shortcut for --with-key=tpm2\n"
|
||||
" --tpm2-device=PATH\n"
|
||||
" Pick TPM2 device\n"
|
||||
" --tpm2-pcrs=PCR1+PCR2+PCR3+…\n"
|
||||
" Specify TPM2 PCRs to seal against\n"
|
||||
"\nSee the %2$s for details.\n"
|
||||
, program_invocation_short_name
|
||||
, link
|
||||
, ansi_underline(), ansi_normal()
|
||||
, ansi_highlight(), ansi_normal()
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_argv(int argc, char *argv[]) {
|
||||
|
||||
enum {
|
||||
ARG_VERSION = 0x100,
|
||||
ARG_NO_PAGER,
|
||||
ARG_NO_LEGEND,
|
||||
ARG_JSON,
|
||||
ARG_SYSTEM,
|
||||
ARG_TRANSCODE,
|
||||
ARG_NEWLINE,
|
||||
ARG_WITH_KEY,
|
||||
ARG_TPM2_DEVICE,
|
||||
ARG_TPM2_PCRS,
|
||||
ARG_NAME,
|
||||
ARG_TIMESTAMP,
|
||||
ARG_NOT_AFTER,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, ARG_VERSION },
|
||||
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
|
||||
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
|
||||
{ "json", required_argument, NULL, ARG_JSON },
|
||||
{ "system", no_argument, NULL, ARG_SYSTEM },
|
||||
{ "transcode", required_argument, NULL, ARG_TRANSCODE },
|
||||
{ "newline", required_argument, NULL, ARG_NEWLINE },
|
||||
{ "pretty", no_argument, NULL, 'p' },
|
||||
{ "with-key", required_argument, NULL, ARG_WITH_KEY },
|
||||
{ "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
|
||||
{ "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS },
|
||||
{ "name", required_argument, NULL, ARG_NAME },
|
||||
{ "timestamp", required_argument, NULL, ARG_TIMESTAMP },
|
||||
{ "not-after", required_argument, NULL, ARG_NOT_AFTER },
|
||||
{}
|
||||
};
|
||||
|
||||
int c, r;
|
||||
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
while ((c = getopt_long(argc, argv, "hHTp", options, NULL)) >= 0) {
|
||||
|
||||
switch (c) {
|
||||
|
||||
case 'h':
|
||||
return verb_help(0, NULL, NULL);
|
||||
|
||||
case ARG_VERSION:
|
||||
return version();
|
||||
|
||||
case ARG_NO_PAGER:
|
||||
arg_pager_flags |= PAGER_DISABLE;
|
||||
break;
|
||||
|
||||
case ARG_NO_LEGEND:
|
||||
arg_legend = false;
|
||||
break;
|
||||
|
||||
case ARG_JSON:
|
||||
r = parse_json_argument(optarg, &arg_json_format_flags);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case ARG_SYSTEM:
|
||||
arg_system = true;
|
||||
break;
|
||||
|
||||
case ARG_TRANSCODE:
|
||||
if (parse_boolean(optarg) == 0) /* If specified as "false", turn transcoding off */
|
||||
arg_transcode = TRANSCODE_OFF;
|
||||
else {
|
||||
TranscodeMode m;
|
||||
|
||||
m = transcode_mode_from_string(optarg);
|
||||
if (m < 0)
|
||||
return log_error_errno(m, "Failed to parse transcode mode: %m");
|
||||
|
||||
arg_transcode = m;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ARG_NEWLINE:
|
||||
if (isempty(optarg) || streq(optarg, "auto"))
|
||||
arg_newline = -1;
|
||||
else {
|
||||
bool b;
|
||||
|
||||
r = parse_boolean_argument("--newline=", optarg, &b);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
arg_newline = b;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
arg_pretty = true;
|
||||
break;
|
||||
|
||||
case ARG_WITH_KEY:
|
||||
if (isempty(optarg) || streq(optarg, "auto"))
|
||||
arg_with_key = SD_ID128_NULL;
|
||||
else if (streq(optarg, "host"))
|
||||
arg_with_key = CRED_AES256_GCM_BY_HOST;
|
||||
else if (streq(optarg, "tpm2"))
|
||||
arg_with_key = CRED_AES256_GCM_BY_TPM2_HMAC;
|
||||
else if (STR_IN_SET(optarg, "host+tpm2", "tpm2+host"))
|
||||
arg_with_key = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC;
|
||||
else
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown key type: %s", optarg);
|
||||
|
||||
break;
|
||||
|
||||
case 'H':
|
||||
arg_with_key = CRED_AES256_GCM_BY_HOST;
|
||||
break;
|
||||
|
||||
case 'T':
|
||||
arg_with_key = CRED_AES256_GCM_BY_TPM2_HMAC;
|
||||
break;
|
||||
|
||||
case ARG_TPM2_DEVICE: {
|
||||
_cleanup_free_ char *device = NULL;
|
||||
|
||||
if (streq(optarg, "list"))
|
||||
return tpm2_list_devices();
|
||||
|
||||
if (!streq(optarg, "auto")) {
|
||||
device = strdup(optarg);
|
||||
if (!device)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
arg_tpm2_device = TAKE_PTR(device);
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_TPM2_PCRS: {
|
||||
uint32_t mask;
|
||||
|
||||
if (isempty(optarg)) {
|
||||
arg_tpm2_pcr_mask = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
r = tpm2_parse_pcrs(optarg, &mask);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (arg_tpm2_pcr_mask == UINT32_MAX)
|
||||
arg_tpm2_pcr_mask = mask;
|
||||
else
|
||||
arg_tpm2_pcr_mask |= mask;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_NAME:
|
||||
if (isempty(optarg)) {
|
||||
arg_name = NULL;
|
||||
arg_name_any = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!credential_name_valid(optarg))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid credential name: %s", optarg);
|
||||
|
||||
arg_name = optarg;
|
||||
arg_name_any = false;
|
||||
break;
|
||||
|
||||
case ARG_TIMESTAMP:
|
||||
r = parse_timestamp(optarg, &arg_timestamp);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse timestamp: %s", optarg);
|
||||
|
||||
break;
|
||||
|
||||
case ARG_NOT_AFTER:
|
||||
r = parse_timestamp(optarg, &arg_not_after);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse --not-after= timestamp: %s", optarg);
|
||||
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
default:
|
||||
assert_not_reached("Unhandled option");
|
||||
}
|
||||
}
|
||||
|
||||
if (arg_tpm2_pcr_mask == UINT32_MAX)
|
||||
arg_tpm2_pcr_mask = TPM2_PCR_MASK_DEFAULT;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int creds_main(int argc, char *argv[]) {
|
||||
|
||||
static const Verb verbs[] = {
|
||||
{ "list", VERB_ANY, 1, VERB_DEFAULT, verb_list },
|
||||
{ "cat", 2, VERB_ANY, 0, verb_cat },
|
||||
{ "encrypt", 3, 3, 0, verb_encrypt },
|
||||
{ "decrypt", 2, 3, 0, verb_decrypt },
|
||||
{ "setup", VERB_ANY, 1, 0, verb_setup },
|
||||
{ "help", VERB_ANY, 1, 0, verb_help },
|
||||
{}
|
||||
};
|
||||
|
||||
return dispatch_verb(argc, argv, verbs, NULL);
|
||||
}
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
int r;
|
||||
|
||||
log_setup();
|
||||
|
||||
r = parse_argv(argc, argv);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
return creds_main(argc, argv);
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
|
@ -1902,19 +1902,10 @@ static int setup_keys(void) {
|
||||
if (fd < 0)
|
||||
return log_error_errno(fd, "Failed to open %s: %m", k);
|
||||
|
||||
/* Enable secure remove, exclusion from dump, synchronous writing and in-place updating */
|
||||
static const unsigned chattr_flags[] = {
|
||||
FS_SECRM_FL,
|
||||
FS_NODUMP_FL,
|
||||
FS_SYNC_FL,
|
||||
FS_NOCOW_FL,
|
||||
};
|
||||
for (size_t j = 0; j < ELEMENTSOF(chattr_flags); j++) {
|
||||
r = chattr_fd(fd, chattr_flags[j], chattr_flags[j], NULL);
|
||||
if (r < 0)
|
||||
log_full_errno(ERRNO_IS_NOT_SUPPORTED(r) ? LOG_DEBUG : LOG_WARNING, r,
|
||||
"Failed to set file attribute 0x%x: %m", chattr_flags[j]);
|
||||
}
|
||||
r = chattr_secret(fd, CHATTR_WARN_UNSUPPORTED_FLAGS);
|
||||
if (r < 0)
|
||||
log_full_errno(ERRNO_IS_NOT_SUPPORTED(r) ? LOG_DEBUG : LOG_WARNING,
|
||||
r, "Failed to set file attributes on '%s', ignoring: %m", k);
|
||||
|
||||
struct FSSHeader h = {
|
||||
.signature = { 'K', 'S', 'H', 'H', 'R', 'H', 'L', 'P' },
|
||||
|
@ -166,7 +166,8 @@ libsystemd_static = static_library(
|
||||
include_directories : libsystemd_includes,
|
||||
link_with : libbasic,
|
||||
dependencies : [threads,
|
||||
librt],
|
||||
librt,
|
||||
libopenssl],
|
||||
c_args : libsystemd_c_args)
|
||||
|
||||
libsystemd_sym = files('libsystemd.sym')
|
||||
|
@ -4,6 +4,11 @@
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if HAVE_OPENSSL
|
||||
#include <openssl/hmac.h>
|
||||
#include <openssl/sha.h>
|
||||
#endif
|
||||
|
||||
#include "sd-id128.h"
|
||||
|
||||
#include "alloc-util.h"
|
||||
@ -11,7 +16,9 @@
|
||||
#include "hexdecoct.h"
|
||||
#include "id128-util.h"
|
||||
#include "io-util.h"
|
||||
#if !HAVE_OPENSSL
|
||||
#include "khash.h"
|
||||
#endif
|
||||
#include "macro.h"
|
||||
#include "missing_syscall.h"
|
||||
#include "random-util.h"
|
||||
@ -271,13 +278,28 @@ _public_ int sd_id128_randomize(sd_id128_t *ret) {
|
||||
}
|
||||
|
||||
static int get_app_specific(sd_id128_t base, sd_id128_t app_id, sd_id128_t *ret) {
|
||||
_cleanup_(khash_unrefp) khash *h = NULL;
|
||||
sd_id128_t result;
|
||||
const void *p;
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
#if HAVE_OPENSSL
|
||||
/* We prefer doing this in-process, since we this means we are not dependent on kernel configuration,
|
||||
* and this also works in locked down container environments. But some distros don't like OpenSSL's
|
||||
* license and its (in-) compatibility with GPL2, hence also support khash */
|
||||
uint8_t md[256/8];
|
||||
if (!HMAC(EVP_sha256(),
|
||||
&base, sizeof(base),
|
||||
(const unsigned char*) &app_id, sizeof(app_id),
|
||||
md, NULL))
|
||||
return -ENOTRECOVERABLE;
|
||||
|
||||
/* Take only the first half. */
|
||||
memcpy(&result, md, MIN(sizeof(md), sizeof(result)));
|
||||
#else
|
||||
_cleanup_(khash_unrefp) khash *h = NULL;
|
||||
const void *p;
|
||||
int r;
|
||||
|
||||
r = khash_new_with_key(&h, "hmac(sha256)", &base, sizeof(base));
|
||||
if (r < 0)
|
||||
return r;
|
||||
@ -292,6 +314,7 @@ static int get_app_specific(sd_id128_t base, sd_id128_t app_id, sd_id128_t *ret)
|
||||
|
||||
/* We chop off the trailing 16 bytes */
|
||||
memcpy(&result, p, MIN(khash_get_size(h), sizeof(result)));
|
||||
#endif
|
||||
|
||||
*ret = id128_make_v4_uuid(result);
|
||||
return 0;
|
||||
|
@ -57,21 +57,16 @@ int block_get_whole_disk(dev_t d, dev_t *ret) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int get_block_device(const char *path, dev_t *ret) {
|
||||
_cleanup_close_ int fd = -1;
|
||||
int get_block_device_fd(int fd, dev_t *ret) {
|
||||
struct stat st;
|
||||
int r;
|
||||
|
||||
assert(path);
|
||||
assert(fd >= 0);
|
||||
assert(ret);
|
||||
|
||||
/* Gets the block device directly backing a file system. If the block device is encrypted, returns
|
||||
* the device mapper block device. */
|
||||
|
||||
fd = open(path, O_RDONLY|O_NOFOLLOW|O_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return -errno;
|
||||
|
||||
if (fstat(fd, &st))
|
||||
return -errno;
|
||||
|
||||
@ -90,6 +85,19 @@ int get_block_device(const char *path, dev_t *ret) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int get_block_device(const char *path, dev_t *ret) {
|
||||
_cleanup_close_ int fd = -1;
|
||||
|
||||
assert(path);
|
||||
assert(ret);
|
||||
|
||||
fd = open(path, O_RDONLY|O_NOFOLLOW|O_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return -errno;
|
||||
|
||||
return get_block_device_fd(fd, ret);
|
||||
}
|
||||
|
||||
int block_get_originating(dev_t dt, dev_t *ret) {
|
||||
_cleanup_closedir_ DIR *d = NULL;
|
||||
_cleanup_free_ char *t = NULL;
|
||||
@ -326,6 +334,22 @@ static int blockdev_is_encrypted(const char *sysfs_path, unsigned depth_left) {
|
||||
return found_encrypted;
|
||||
}
|
||||
|
||||
int fd_is_encrypted(int fd) {
|
||||
char p[SYS_BLOCK_PATH_MAX(NULL)];
|
||||
dev_t devt;
|
||||
int r;
|
||||
|
||||
r = get_block_device_fd(fd, &devt);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) /* doesn't have a block device */
|
||||
return false;
|
||||
|
||||
xsprintf_sys_block_path(p, NULL, devt);
|
||||
|
||||
return blockdev_is_encrypted(p, 10 /* safety net: maximum recursion depth */);
|
||||
}
|
||||
|
||||
int path_is_encrypted(const char *path) {
|
||||
char p[SYS_BLOCK_PATH_MAX(NULL)];
|
||||
dev_t devt;
|
||||
|
@ -15,6 +15,7 @@
|
||||
int block_get_whole_disk(dev_t d, dev_t *ret);
|
||||
int block_get_originating(dev_t d, dev_t *ret);
|
||||
|
||||
int get_block_device_fd(int fd, dev_t *ret);
|
||||
int get_block_device(const char *path, dev_t *dev);
|
||||
|
||||
int get_block_device_harder(const char *path, dev_t *dev);
|
||||
@ -23,4 +24,5 @@ int lock_whole_block_device(dev_t devt, int operation);
|
||||
|
||||
int blockdev_partscan_enabled(int fd);
|
||||
|
||||
int fd_is_encrypted(int fd);
|
||||
int path_is_encrypted(const char *path);
|
||||
|
@ -1046,12 +1046,12 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (streq(field, "SetCredential")) {
|
||||
if (STR_IN_SET(field, "SetCredential", "SetCredentialEncrypted")) {
|
||||
r = sd_bus_message_open_container(m, 'r', "sv");
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_message_append_basic(m, 's', "SetCredential");
|
||||
r = sd_bus_message_append_basic(m, 's', field);
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
@ -1062,21 +1062,16 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
|
||||
if (isempty(eq))
|
||||
r = sd_bus_message_append(m, "a(say)", 0);
|
||||
else {
|
||||
_cleanup_free_ char *word = NULL, *unescaped = NULL;
|
||||
_cleanup_free_ char *word = NULL;
|
||||
const char *p = eq;
|
||||
int l;
|
||||
|
||||
r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
|
||||
if (r == -ENOMEM)
|
||||
return log_oom();
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse SetCredential= parameter: %s", eq);
|
||||
return log_error_errno(r, "Failed to parse %s= parameter: %s", field, eq);
|
||||
if (r == 0 || !p)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing argument to SetCredential=.");
|
||||
|
||||
l = cunescape(p, UNESCAPE_ACCEPT_NUL, &unescaped);
|
||||
if (l < 0)
|
||||
return log_error_errno(l, "Failed to unescape SetCredential= value: %s", p);
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing argument to %s=.", field);
|
||||
|
||||
r = sd_bus_message_open_container(m, 'a', "(say)");
|
||||
if (r < 0)
|
||||
@ -1090,7 +1085,25 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_message_append_array(m, 'y', unescaped, l);
|
||||
if (streq(field, "SetCredentialEncrypted")) {
|
||||
_cleanup_free_ void *decoded = NULL;
|
||||
size_t decoded_size;
|
||||
|
||||
r = unbase64mem(p, SIZE_MAX, &decoded, &decoded_size);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to base64 decode encrypted credential: %m");
|
||||
|
||||
r = sd_bus_message_append_array(m, 'y', decoded, decoded_size);
|
||||
} else {
|
||||
_cleanup_free_ char *unescaped = NULL;
|
||||
int l;
|
||||
|
||||
l = cunescape(p, UNESCAPE_ACCEPT_NUL, &unescaped);
|
||||
if (l < 0)
|
||||
return log_error_errno(l, "Failed to unescape %s= value: %s", field, p);
|
||||
|
||||
r = sd_bus_message_append_array(m, 'y', unescaped, l);
|
||||
}
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
@ -1114,12 +1127,12 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (streq(field, "LoadCredential")) {
|
||||
if (STR_IN_SET(field, "LoadCredential", "LoadCredentialEncrypted")) {
|
||||
r = sd_bus_message_open_container(m, 'r', "sv");
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_message_append_basic(m, 's', "LoadCredential");
|
||||
r = sd_bus_message_append_basic(m, 's', field);
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
@ -1137,9 +1150,9 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
|
||||
if (r == -ENOMEM)
|
||||
return log_oom();
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse LoadCredential= parameter: %s", eq);
|
||||
return log_error_errno(r, "Failed to parse %s= parameter: %s", field, eq);
|
||||
if (r == 0 || !p)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing argument to LoadCredential=.");
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing argument to %s=.", field);
|
||||
|
||||
r = sd_bus_message_append(m, "a(ss)", 1, word, p);
|
||||
}
|
||||
|
930
src/shared/creds-util.c
Normal file
930
src/shared/creds-util.c
Normal file
@ -0,0 +1,930 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <sys/file.h>
|
||||
|
||||
#if HAVE_OPENSSL
|
||||
#include <openssl/err.h>
|
||||
#endif
|
||||
|
||||
#include "sd-id128.h"
|
||||
|
||||
#include "blockdev-util.h"
|
||||
#include "chattr-util.h"
|
||||
#include "creds-util.h"
|
||||
#include "env-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "fs-util.h"
|
||||
#include "io-util.h"
|
||||
#include "memory-util.h"
|
||||
#include "mkdir.h"
|
||||
#include "openssl-util.h"
|
||||
#include "path-util.h"
|
||||
#include "random-util.h"
|
||||
#include "sparse-endian.h"
|
||||
#include "stat-util.h"
|
||||
#include "tpm2-util.h"
|
||||
#include "virt.h"
|
||||
|
||||
bool credential_name_valid(const char *s) {
|
||||
/* We want that credential names are both valid in filenames (since that's our primary way to pass
|
||||
* them around) and as fdnames (which is how we might want to pass them around eventually) */
|
||||
return filename_is_valid(s) && fdname_is_valid(s);
|
||||
}
|
||||
|
||||
int get_credentials_dir(const char **ret) {
|
||||
const char *e;
|
||||
|
||||
assert(ret);
|
||||
|
||||
e = secure_getenv("CREDENTIALS_DIRECTORY");
|
||||
if (!e)
|
||||
return -ENXIO;
|
||||
|
||||
if (!path_is_absolute(e) || !path_is_normalized(e))
|
||||
return -EINVAL;
|
||||
|
||||
*ret = e;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int read_credential(const char *name, void **ret, size_t *ret_size) {
|
||||
_cleanup_free_ char *fn = NULL;
|
||||
const char *d;
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
if (!credential_name_valid(name))
|
||||
return -EINVAL;
|
||||
|
||||
r = get_credentials_dir(&d);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
fn = path_join(d, name);
|
||||
if (!fn)
|
||||
return -ENOMEM;
|
||||
|
||||
return read_full_file_full(
|
||||
AT_FDCWD, fn,
|
||||
UINT64_MAX, SIZE_MAX,
|
||||
READ_FULL_FILE_SECURE,
|
||||
NULL,
|
||||
(char**) ret, ret_size);
|
||||
}
|
||||
|
||||
#if HAVE_OPENSSL
|
||||
|
||||
#define CREDENTIAL_HOST_SECRET_SIZE 4096
|
||||
|
||||
static const sd_id128_t credential_app_id =
|
||||
SD_ID128_MAKE(d3,ac,ec,ba,0d,ad,4c,df,b8,c9,38,15,28,93,6c,58);
|
||||
|
||||
struct credential_host_secret_format {
|
||||
/* The hashed machine ID of the machine this belongs to. Why? We want to ensure that each machine
|
||||
* gets its own secret, even if people forget to flush out this secret file. Hence we bind it to the
|
||||
* machine ID, for which there's hopefully a better chance it will be flushed out. We use a hashed
|
||||
* machine ID instead of the literal one, because it's trivial to, and it might be a good idea not
|
||||
* being able to directly associate a secret key file with a host. */
|
||||
sd_id128_t machine_id;
|
||||
|
||||
/* The actual secret key */
|
||||
uint8_t data[CREDENTIAL_HOST_SECRET_SIZE];
|
||||
} _packed_;
|
||||
|
||||
static int make_credential_host_secret(
|
||||
int dfd,
|
||||
const sd_id128_t machine_id,
|
||||
const char *fn,
|
||||
void **ret_data,
|
||||
size_t *ret_size) {
|
||||
|
||||
struct credential_host_secret_format buf;
|
||||
_cleanup_free_ char *t = NULL;
|
||||
_cleanup_close_ int fd = -1;
|
||||
int r;
|
||||
|
||||
assert(dfd >= 0);
|
||||
assert(fn);
|
||||
|
||||
fd = openat(dfd, ".", O_CLOEXEC|O_WRONLY|O_TMPFILE, 0400);
|
||||
if (fd < 0) {
|
||||
log_debug_errno(errno, "Failed to create temporary credential file with O_TMPFILE, proceeding without: %m");
|
||||
|
||||
if (asprintf(&t, "credential.secret.%016" PRIx64, random_u64()) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
fd = openat(dfd, t, O_CLOEXEC|O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW, 0400);
|
||||
if (fd < 0)
|
||||
return -errno;
|
||||
}
|
||||
|
||||
r = chattr_secret(fd, 0);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to set file attributes for secrets file, ignoring: %m");
|
||||
|
||||
buf = (struct credential_host_secret_format) {
|
||||
.machine_id = machine_id,
|
||||
};
|
||||
|
||||
r = genuine_random_bytes(buf.data, sizeof(buf.data), RANDOM_BLOCK);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
r = loop_write(fd, &buf, sizeof(buf), false);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
if (fsync(fd) < 0) {
|
||||
r = -errno;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (t) {
|
||||
r = rename_noreplace(dfd, t, dfd, fn);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
t = mfree(t);
|
||||
} else if (linkat(fd, "", dfd, fn, AT_EMPTY_PATH) < 0) {
|
||||
r = -errno;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (fsync(dfd) < 0) {
|
||||
r = -errno;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (ret_data) {
|
||||
void *copy;
|
||||
|
||||
copy = memdup(buf.data, sizeof(buf.data));
|
||||
if (!copy) {
|
||||
r = -ENOMEM;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
*ret_data = copy;
|
||||
}
|
||||
|
||||
if (ret_size)
|
||||
*ret_size = sizeof(buf.data);
|
||||
|
||||
r = 0;
|
||||
|
||||
finish:
|
||||
if (t && unlinkat(dfd, t, 0) < 0)
|
||||
log_debug_errno(errno, "Failed to remove temporary credential key: %m");
|
||||
|
||||
explicit_bzero_safe(&buf, sizeof(buf));
|
||||
return r;
|
||||
}
|
||||
|
||||
int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *ret_size) {
|
||||
_cleanup_free_ char *efn = NULL, *ep = NULL;
|
||||
_cleanup_close_ int dfd = -1;
|
||||
sd_id128_t machine_id;
|
||||
const char *e, *fn, *p;
|
||||
int r;
|
||||
|
||||
r = sd_id128_get_machine_app_specific(credential_app_id, &machine_id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
e = secure_getenv("SYSTEMD_CREDENTIAL_SECRET");
|
||||
if (e) {
|
||||
if (!path_is_normalized(e))
|
||||
return -EINVAL;
|
||||
if (!path_is_absolute(e))
|
||||
return -EINVAL;
|
||||
|
||||
r = path_extract_directory(e, &ep);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = path_extract_filename(e, &efn);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
p = ep;
|
||||
fn = efn;
|
||||
} else {
|
||||
p = "/var/lib/systemd";
|
||||
fn = "credential.secret";
|
||||
}
|
||||
|
||||
(void) mkdir_p(p, 0755);
|
||||
dfd = open(p, O_CLOEXEC|O_DIRECTORY|O_RDONLY);
|
||||
if (dfd < 0)
|
||||
return -errno;
|
||||
|
||||
if (FLAGS_SET(flags, CREDENTIAL_SECRET_FAIL_ON_TEMPORARY_FS)) {
|
||||
r = fd_is_temporary_fs(dfd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0)
|
||||
return -ENOMEDIUM;
|
||||
}
|
||||
|
||||
for (unsigned attempt = 0;; attempt++) {
|
||||
_cleanup_(erase_and_freep) struct credential_host_secret_format *f = NULL;
|
||||
_cleanup_close_ int fd = -1;
|
||||
size_t l = 0;
|
||||
ssize_t n = 0;
|
||||
struct stat st;
|
||||
|
||||
if (attempt >= 3) /* Somebody is playing games with us */
|
||||
return -EIO;
|
||||
|
||||
fd = openat(dfd, fn, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_NOFOLLOW);
|
||||
if (fd < 0) {
|
||||
if (errno != ENOENT || !FLAGS_SET(flags, CREDENTIAL_SECRET_GENERATE))
|
||||
return -errno;
|
||||
|
||||
r = make_credential_host_secret(dfd, machine_id, fn, ret, ret_size);
|
||||
if (r == -EEXIST) {
|
||||
log_debug_errno(r, "Credential secret was created while we were creating it. Trying to read new secret.");
|
||||
continue;
|
||||
}
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (fstat(fd, &st) < 0)
|
||||
return -errno;
|
||||
|
||||
r = stat_verify_regular(&st);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (st.st_nlink == 0) /* Deleted by now, try again */
|
||||
continue;
|
||||
if (st.st_nlink > 1)
|
||||
return -EPERM; /* Our deletion check won't work if hardlinked somewhere else */
|
||||
if ((st.st_mode & 07777) != 0400) /* Don't use file if not 0400 access mode */
|
||||
return -EPERM;
|
||||
if (st.st_size > 16*1024*1024)
|
||||
return -E2BIG;
|
||||
l = st.st_size;
|
||||
if (l < offsetof(struct credential_host_secret_format, data) + 1)
|
||||
return -EINVAL;
|
||||
|
||||
f = malloc(l+1);
|
||||
if (!f)
|
||||
return -ENOMEM;
|
||||
|
||||
n = read(fd, f, l+1);
|
||||
if (n < 0)
|
||||
return -errno;
|
||||
if ((size_t) n != l) /* What? The size changed? */
|
||||
return -EIO;
|
||||
|
||||
if (sd_id128_equal(machine_id, f->machine_id)) {
|
||||
size_t sz;
|
||||
|
||||
if (FLAGS_SET(flags, CREDENTIAL_SECRET_WARN_NOT_ENCRYPTED)) {
|
||||
r = fd_is_encrypted(fd);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to determine if credential secret file '%s/%s' is encrypted.", p, fn);
|
||||
else if (r == 0)
|
||||
log_warning("Credential secret file '%s/%s' is not located on encrypted media, using anyway.", p, fn);
|
||||
}
|
||||
|
||||
sz = l - offsetof(struct credential_host_secret_format, data);
|
||||
assert(sz > 0);
|
||||
|
||||
if (ret) {
|
||||
void *copy;
|
||||
|
||||
copy = memdup(f->data, sz);
|
||||
if (!copy)
|
||||
return -ENOMEM;
|
||||
|
||||
*ret = copy;
|
||||
}
|
||||
|
||||
if (ret_size)
|
||||
*ret_size = sz;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Hmm, this secret is from somewhere else. Let's delete the file. Let's first acquire a lock
|
||||
* to ensure we are the only ones accessing the file while we delete it. */
|
||||
|
||||
if (flock(fd, LOCK_EX) < 0)
|
||||
return -errno;
|
||||
|
||||
/* Before we delete it check that the file is still linked into the file system */
|
||||
if (fstat(fd, &st) < 0)
|
||||
return -errno;
|
||||
if (st.st_nlink == 0) /* Already deleted by now? */
|
||||
continue;
|
||||
if (st.st_nlink != 1) /* Safety check, someone is playing games with us */
|
||||
return -EPERM;
|
||||
|
||||
if (unlinkat(dfd, fn, 0) < 0)
|
||||
return -errno;
|
||||
|
||||
/* And now try again */
|
||||
}
|
||||
}
|
||||
|
||||
/* Construction is like this:
|
||||
*
|
||||
* A symmetric encryption key is derived from:
|
||||
*
|
||||
* 1. Either the "host" key (a key stored in /var/lib/credential.secret)
|
||||
*
|
||||
* 2. A key generated by letting the TPM2 calculate an HMAC hash of some nonce we pass to it, keyed
|
||||
* by a key derived from its internal seed key.
|
||||
*
|
||||
* 3. The concatenation of the above.
|
||||
*
|
||||
* The above is hashed with SHA256 which is then used as encryption key for AES256-GCM. The encrypted
|
||||
* credential is a short (unencrypted) header describing which of the three keys to use, the IV to use for
|
||||
* AES256-GCM and some more meta information (sizes of certain objects) that is strictly speaking redundant,
|
||||
* but kinda nice to have since we can have a more generic parser. If the TPM2 key is used this is followed
|
||||
* by another (unencrypted) header, with information about the TPM2 policy used (specifically: the PCR mask
|
||||
* to bind against, and a hash of the resulting policy — the latter being redundant, but speeding up things a
|
||||
* bit, since we can more quickly refuse PCR state), followed by a sealed/exported TPM2 HMAC key. This is
|
||||
* then followed by the encrypted data, which begins with a metadata header (which contains validity
|
||||
* timestamps as well as the credential name), followed by the actual credential payload. The file ends in
|
||||
* the AES256-GCM tag. To make things simple, the AES256-GCM AAD covers the main and the TPM2 header in
|
||||
* full. This means the whole file is either protected by AAD, or is ciphertext, or is the tag. No
|
||||
* unprotected data is included.
|
||||
*/
|
||||
|
||||
struct _packed_ encrypted_credential_header {
|
||||
sd_id128_t id;
|
||||
le32_t key_size;
|
||||
le32_t block_size;
|
||||
le32_t iv_size;
|
||||
le32_t tag_size;
|
||||
uint8_t iv[];
|
||||
/* Followed by NUL bytes until next 8 byte boundary */
|
||||
};
|
||||
|
||||
struct _packed_ tpm2_credential_header {
|
||||
le64_t pcr_mask;
|
||||
le32_t blob_size;
|
||||
le32_t policy_hash_size;
|
||||
uint8_t policy_hash_and_blob[];
|
||||
/* Followed by NUL bytes until next 8 byte boundary */
|
||||
};
|
||||
|
||||
struct _packed_ metadata_credential_header {
|
||||
le64_t timestamp;
|
||||
le64_t not_after;
|
||||
le32_t name_size;
|
||||
char name[];
|
||||
/* Followed by NUL bytes until next 8 byte boundary */
|
||||
};
|
||||
|
||||
/* Some generic limit for parts of the encrypted credential for which we don't know the right size ahead of
|
||||
* time, but where we are really sure it won't be larger than this. Should be larger than any possible IV,
|
||||
* padding, tag size and so on. This is purely used for early filtering out of invalid sizes. */
|
||||
#define CREDENTIAL_FIELD_SIZE_MAX (16U*1024U)
|
||||
|
||||
static int sha256_hash_host_and_tpm2_key(
|
||||
const void *host_key,
|
||||
size_t host_key_size,
|
||||
const void *tpm2_key,
|
||||
size_t tpm2_key_size,
|
||||
uint8_t ret[static SHA256_DIGEST_LENGTH]) {
|
||||
|
||||
SHA256_CTX sha256_context;
|
||||
|
||||
assert(host_key_size == 0 || host_key);
|
||||
assert(tpm2_key_size == 0 || tpm2_key);
|
||||
assert(ret);
|
||||
|
||||
/* Combines the host key and the TPM2 HMAC hash into a SHA256 hash value we'll use as symmetric encryption key. */
|
||||
|
||||
if (SHA256_Init(&sha256_context) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initial SHA256 context.");
|
||||
|
||||
if (host_key && SHA256_Update(&sha256_context, host_key, host_key_size) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to hash host key.");
|
||||
|
||||
if (tpm2_key && SHA256_Update(&sha256_context, tpm2_key, tpm2_key_size) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to hash TPM2 key.");
|
||||
|
||||
if (SHA256_Final(ret, &sha256_context) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize SHA256 hash.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int encrypt_credential_and_warn(
|
||||
sd_id128_t with_key,
|
||||
const char *name,
|
||||
usec_t timestamp,
|
||||
usec_t not_after,
|
||||
const char *tpm2_device,
|
||||
uint32_t tpm2_pcr_mask,
|
||||
const void *input,
|
||||
size_t input_size,
|
||||
void **ret,
|
||||
size_t *ret_size) {
|
||||
|
||||
_cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL;
|
||||
_cleanup_(erase_and_freep) void *host_key = NULL, *tpm2_key = NULL;
|
||||
size_t host_key_size = 0, tpm2_key_size = 0, tpm2_blob_size = 0, tpm2_policy_hash_size = 0, output_size, p, ml;
|
||||
_cleanup_free_ void *tpm2_blob = NULL, *tpm2_policy_hash = NULL, *iv = NULL, *output = NULL;
|
||||
_cleanup_free_ struct metadata_credential_header *m = NULL;
|
||||
struct encrypted_credential_header *h;
|
||||
int ksz, bsz, ivsz, tsz, added, r;
|
||||
uint8_t md[SHA256_DIGEST_LENGTH];
|
||||
const EVP_CIPHER *cc;
|
||||
#if HAVE_TPM2
|
||||
bool try_tpm2 = false;
|
||||
#endif
|
||||
sd_id128_t id;
|
||||
|
||||
assert(input || input_size == 0);
|
||||
assert(ret);
|
||||
assert(ret_size);
|
||||
|
||||
if (name && !credential_name_valid(name))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid credential name: %s", name);
|
||||
|
||||
if (not_after != USEC_INFINITY && timestamp != USEC_INFINITY && not_after < timestamp)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential is invalidated before it is valid (" USEC_FMT " < " USEC_FMT ").", not_after, timestamp);
|
||||
|
||||
if (DEBUG_LOGGING) {
|
||||
char buf[FORMAT_TIMESTAMP_MAX];
|
||||
|
||||
if (name)
|
||||
log_debug("Including credential name '%s' in encrypted credential.", name);
|
||||
if (timestamp != USEC_INFINITY)
|
||||
log_debug("Including timestamp '%s' in encrypted credential.", format_timestamp(buf, sizeof(buf), timestamp));
|
||||
if (not_after != USEC_INFINITY)
|
||||
log_debug("Including not-after timestamp '%s' in encrypted credential.", format_timestamp(buf, sizeof(buf), not_after));
|
||||
}
|
||||
|
||||
if (sd_id128_is_null(with_key) ||
|
||||
sd_id128_in_set(with_key, CRED_AES256_GCM_BY_HOST, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC)) {
|
||||
|
||||
r = get_credential_host_secret(
|
||||
CREDENTIAL_SECRET_GENERATE|
|
||||
CREDENTIAL_SECRET_WARN_NOT_ENCRYPTED|
|
||||
(sd_id128_is_null(with_key) ? CREDENTIAL_SECRET_FAIL_ON_TEMPORARY_FS : 0),
|
||||
&host_key,
|
||||
&host_key_size);
|
||||
if (r == -ENOMEDIUM && sd_id128_is_null(with_key))
|
||||
log_debug_errno(r, "Credential host secret location on temporary file system, not using.");
|
||||
else if (r < 0)
|
||||
return log_error_errno(r, "Failed to determine local credential host secret: %m");
|
||||
}
|
||||
|
||||
#if HAVE_TPM2
|
||||
if (sd_id128_is_null(with_key)) {
|
||||
/* If automatic mode is selected and we are running in a container, let's not try TPM2. OTOH
|
||||
* if user picks TPM2 explicitly, let's always honour the request and try. */
|
||||
|
||||
r = detect_container();
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to determine whether we are running in a container, ignoring: %m");
|
||||
else if (r > 0)
|
||||
log_debug("Running in container, not attempting to use TPM2.");
|
||||
|
||||
try_tpm2 = r <= 0;
|
||||
}
|
||||
|
||||
if (try_tpm2 ||
|
||||
sd_id128_in_set(with_key, CRED_AES256_GCM_BY_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC)) {
|
||||
|
||||
r = tpm2_seal(tpm2_device,
|
||||
tpm2_pcr_mask,
|
||||
&tpm2_key,
|
||||
&tpm2_key_size,
|
||||
&tpm2_blob,
|
||||
&tpm2_blob_size,
|
||||
&tpm2_policy_hash,
|
||||
&tpm2_policy_hash_size);
|
||||
if (r < 0) {
|
||||
if (!sd_id128_is_null(with_key))
|
||||
return r;
|
||||
|
||||
log_debug_errno(r, "TPM2 sealing didn't work, not using: %m");
|
||||
}
|
||||
|
||||
assert(tpm2_blob_size <= CREDENTIAL_FIELD_SIZE_MAX);
|
||||
assert(tpm2_policy_hash_size <= CREDENTIAL_FIELD_SIZE_MAX);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (sd_id128_is_null(with_key)) {
|
||||
/* Let's settle the key type in auto mode now. */
|
||||
|
||||
if (host_key && tpm2_key)
|
||||
id = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC;
|
||||
else if (tpm2_key)
|
||||
id = CRED_AES256_GCM_BY_TPM2_HMAC;
|
||||
else if (host_key)
|
||||
id = CRED_AES256_GCM_BY_HOST;
|
||||
else
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
|
||||
"TPM2 not available and host key located on temporary file system, no encryption key available.");
|
||||
} else
|
||||
id = with_key;
|
||||
|
||||
/* Let's now take the host key and the TPM2 key and hash it together, to use as encryption key for the data */
|
||||
r = sha256_hash_host_and_tpm2_key(host_key, host_key_size, tpm2_key, tpm2_key_size, md);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
assert_se(cc = EVP_aes_256_gcm());
|
||||
|
||||
ksz = EVP_CIPHER_key_length(cc);
|
||||
assert(ksz == sizeof(md));
|
||||
|
||||
bsz = EVP_CIPHER_block_size(cc);
|
||||
assert(bsz > 0);
|
||||
assert((size_t) bsz <= CREDENTIAL_FIELD_SIZE_MAX);
|
||||
|
||||
ivsz = EVP_CIPHER_iv_length(cc);
|
||||
if (ivsz > 0) {
|
||||
assert((size_t) ivsz <= CREDENTIAL_FIELD_SIZE_MAX);
|
||||
|
||||
iv = malloc(ivsz);
|
||||
if (!iv)
|
||||
return log_oom();
|
||||
|
||||
r = genuine_random_bytes(iv, ivsz, RANDOM_BLOCK);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to acquired randomized IV: %m");
|
||||
}
|
||||
|
||||
tsz = 16; /* FIXME: On OpenSSL 3 there is EVP_CIPHER_CTX_get_tag_length(), until then let's hardcode this */
|
||||
|
||||
context = EVP_CIPHER_CTX_new();
|
||||
if (!context)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOMEM), "Failed to allocate encryption object: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
if (EVP_EncryptInit_ex(context, cc, NULL, md, iv) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize encryption context: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
/* Just an upper estimate */
|
||||
output_size =
|
||||
ALIGN8(offsetof(struct encrypted_credential_header, iv) + ivsz) +
|
||||
ALIGN8(tpm2_key ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob_size + tpm2_policy_hash_size : 0) +
|
||||
ALIGN8(offsetof(struct metadata_credential_header, name) + strlen_ptr(name)) +
|
||||
input_size + 2U * (size_t) bsz +
|
||||
tsz;
|
||||
|
||||
output = malloc0(output_size);
|
||||
if (!output)
|
||||
return log_oom();
|
||||
|
||||
h = (struct encrypted_credential_header*) output;
|
||||
h->id = id;
|
||||
h->block_size = htole32(bsz);
|
||||
h->key_size = htole32(ksz);
|
||||
h->tag_size = htole32(tsz);
|
||||
h->iv_size = htole32(ivsz);
|
||||
memcpy(h->iv, iv, ivsz);
|
||||
|
||||
p = ALIGN8(offsetof(struct encrypted_credential_header, iv) + ivsz);
|
||||
|
||||
if (tpm2_key) {
|
||||
struct tpm2_credential_header *t;
|
||||
|
||||
t = (struct tpm2_credential_header*) ((uint8_t*) output + p);
|
||||
t->pcr_mask = htole64(tpm2_pcr_mask);
|
||||
t->blob_size = htole32(tpm2_blob_size);
|
||||
t->policy_hash_size = htole32(tpm2_policy_hash_size);
|
||||
memcpy(t->policy_hash_and_blob, tpm2_blob, tpm2_blob_size);
|
||||
memcpy(t->policy_hash_and_blob + tpm2_blob_size, tpm2_policy_hash, tpm2_policy_hash_size);
|
||||
|
||||
p += ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob_size + tpm2_policy_hash_size);
|
||||
}
|
||||
|
||||
/* Pass the encrypted + TPM2 header as AAD */
|
||||
if (EVP_EncryptUpdate(context, NULL, &added, output, p) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to write AAD data: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
/* Now construct the metadata header */
|
||||
ml = strlen_ptr(name);
|
||||
m = malloc0(ALIGN8(offsetof(struct metadata_credential_header, name) + ml));
|
||||
if (!m)
|
||||
return log_oom();
|
||||
|
||||
m->timestamp = htole64(timestamp);
|
||||
m->not_after = htole64(not_after);
|
||||
m->name_size = htole32(ml);
|
||||
memcpy_safe(m->name, name, ml);
|
||||
|
||||
/* And encrypt the metadata header */
|
||||
if (EVP_EncryptUpdate(context, (uint8_t*) output + p, &added, (const unsigned char*) m, ALIGN8(offsetof(struct metadata_credential_header, name) + ml)) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt metadata header: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
assert(added >= 0);
|
||||
assert((size_t) added <= output_size - p);
|
||||
p += added;
|
||||
|
||||
/* Then encrypt the plaintext */
|
||||
if (EVP_EncryptUpdate(context, (uint8_t*) output + p, &added, input, input_size) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt data: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
assert(added >= 0);
|
||||
assert((size_t) added <= output_size - p);
|
||||
p += added;
|
||||
|
||||
/* Finalize */
|
||||
if (EVP_EncryptFinal_ex(context, (uint8_t*) output + p, &added) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize data encryption: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
assert(added >= 0);
|
||||
assert((size_t) added <= output_size - p);
|
||||
p += added;
|
||||
|
||||
assert(p <= output_size - tsz);
|
||||
|
||||
/* Append tag */
|
||||
if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, tsz, (uint8_t*) output + p) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get tag: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
p += tsz;
|
||||
assert(p <= output_size);
|
||||
|
||||
if (DEBUG_LOGGING) {
|
||||
size_t base64_size;
|
||||
|
||||
base64_size = DIV_ROUND_UP(p * 4, 3); /* Include base64 size increase in debug output */
|
||||
|
||||
log_debug("Input of %zu bytes grew to output of %zu bytes (+%2zu%%).", input_size, base64_size, base64_size * 100 / input_size - 100);
|
||||
}
|
||||
|
||||
*ret = TAKE_PTR(output);
|
||||
*ret_size = p;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int decrypt_credential_and_warn(
|
||||
const char *validate_name,
|
||||
usec_t validate_timestamp,
|
||||
const char *tpm2_device,
|
||||
const void *input,
|
||||
size_t input_size,
|
||||
void **ret,
|
||||
size_t *ret_size) {
|
||||
|
||||
_cleanup_(erase_and_freep) void *host_key = NULL, *tpm2_key = NULL, *plaintext = NULL;
|
||||
_cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL;
|
||||
size_t host_key_size = 0, tpm2_key_size = 0, plaintext_size, p, hs;
|
||||
struct encrypted_credential_header *h;
|
||||
struct metadata_credential_header *m;
|
||||
uint8_t md[SHA256_DIGEST_LENGTH];
|
||||
bool with_tpm2, with_host_key;
|
||||
const EVP_CIPHER *cc;
|
||||
int r, added;
|
||||
|
||||
assert(input || input_size == 0);
|
||||
assert(ret);
|
||||
assert(ret_size);
|
||||
|
||||
h = (struct encrypted_credential_header*) input;
|
||||
|
||||
/* The ID must fit in, for the current and all future formats */
|
||||
if (input_size < sizeof(h->id))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
|
||||
|
||||
with_host_key = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_HOST, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC);
|
||||
with_tpm2 = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC);
|
||||
|
||||
if (!with_host_key && !with_tpm2)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unknown encryption format, or corrupted data: %m");
|
||||
|
||||
/* Now we know the minimum header size */
|
||||
if (input_size < offsetof(struct encrypted_credential_header, iv))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
|
||||
|
||||
/* Verify some basic header values */
|
||||
if (le32toh(h->key_size) != sizeof(md))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected key size in header.");
|
||||
if (le32toh(h->block_size) <= 0 || le32toh(h->block_size) > CREDENTIAL_FIELD_SIZE_MAX)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected block size in header.");
|
||||
if (le32toh(h->iv_size) > CREDENTIAL_FIELD_SIZE_MAX)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "IV size too large.");
|
||||
if (le32toh(h->tag_size) != 16) /* FIXME: On OpenSSL 3, let's verify via EVP_CIPHER_CTX_get_tag_length() */
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected tag size in header.");
|
||||
|
||||
/* Ensure we have space for the full header now (we don't know the size of the name hence this is a
|
||||
* lower limit only) */
|
||||
if (input_size <
|
||||
ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) +
|
||||
ALIGN8((with_tpm2 ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) : 0)) +
|
||||
ALIGN8(offsetof(struct metadata_credential_header, name)) +
|
||||
le32toh(h->tag_size))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
|
||||
|
||||
p = ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size));
|
||||
|
||||
if (with_tpm2) {
|
||||
#if HAVE_TPM2
|
||||
struct tpm2_credential_header* t = (struct tpm2_credential_header*) ((uint8_t*) input + p);
|
||||
|
||||
if (le64toh(t->pcr_mask) >= (UINT64_C(1) << TPM2_PCRS_MAX))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "TPM2 PCR mask out of range.");
|
||||
if (le32toh(t->blob_size) > CREDENTIAL_FIELD_SIZE_MAX)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected TPM2 blob size.");
|
||||
if (le32toh(t->policy_hash_size) > CREDENTIAL_FIELD_SIZE_MAX)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected TPM2 policy hash size.");
|
||||
|
||||
/* Ensure we have space for the full TPM2 header now (still don't know the name, and its size
|
||||
* though, hence still just a lower limit test only) */
|
||||
if (input_size <
|
||||
ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) +
|
||||
ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + le32toh(t->blob_size) + le32toh(t->policy_hash_size)) +
|
||||
ALIGN8(offsetof(struct metadata_credential_header, name)) +
|
||||
le32toh(h->tag_size))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
|
||||
|
||||
r = tpm2_unseal(tpm2_device,
|
||||
le64toh(t->pcr_mask),
|
||||
t->policy_hash_and_blob,
|
||||
le32toh(t->blob_size),
|
||||
t->policy_hash_and_blob + le32toh(t->blob_size),
|
||||
le32toh(t->policy_hash_size),
|
||||
&tpm2_key,
|
||||
&tpm2_key_size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
p += ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) +
|
||||
le32toh(t->blob_size) +
|
||||
le32toh(t->policy_hash_size));
|
||||
#else
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Credential requires TPM2 support, but TPM2 support not available.");
|
||||
#endif
|
||||
}
|
||||
|
||||
if (with_host_key) {
|
||||
r = get_credential_host_secret(
|
||||
0,
|
||||
&host_key,
|
||||
&host_key_size);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to determine local credential key: %m");
|
||||
}
|
||||
|
||||
sha256_hash_host_and_tpm2_key(host_key, host_key_size, tpm2_key, tpm2_key_size, md);
|
||||
|
||||
assert_se(cc = EVP_aes_256_gcm());
|
||||
|
||||
/* Make sure cipher expectations match the header */
|
||||
if (EVP_CIPHER_key_length(cc) != (int) le32toh(h->key_size))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected key size in header.");
|
||||
if (EVP_CIPHER_block_size(cc) != (int) le32toh(h->block_size))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected block size in header.");
|
||||
|
||||
context = EVP_CIPHER_CTX_new();
|
||||
if (!context)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOMEM), "Failed to allocate decryption object: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
if (EVP_DecryptInit_ex(context, cc, NULL, NULL, NULL) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize decryption context: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, le32toh(h->iv_size), NULL) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set IV size on decryption context: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
if (EVP_DecryptInit_ex(context, NULL, NULL, md, h->iv) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set IV and key: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
if (EVP_DecryptUpdate(context, NULL, &added, input, p) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to write AAD data: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
plaintext = malloc(input_size - p - le32toh(h->tag_size));
|
||||
if (!plaintext)
|
||||
return -ENOMEM;
|
||||
|
||||
if (EVP_DecryptUpdate(
|
||||
context,
|
||||
plaintext,
|
||||
&added,
|
||||
(uint8_t*) input + p,
|
||||
input_size - p - le32toh(h->tag_size)) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decrypt data: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
assert(added >= 0);
|
||||
assert((size_t) added <= input_size - p - le32toh(h->tag_size));
|
||||
plaintext_size = added;
|
||||
|
||||
if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_TAG, le32toh(h->tag_size), (uint8_t*) input + input_size - le32toh(h->tag_size)) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set tag: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
if (EVP_DecryptFinal_ex(context, (uint8_t*) plaintext + plaintext_size, &added) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Decryption failed (incorrect key?): %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
plaintext_size += added;
|
||||
|
||||
if (plaintext_size < ALIGN8(offsetof(struct metadata_credential_header, name)))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Metadata header incomplete.");
|
||||
|
||||
m = plaintext;
|
||||
|
||||
if (le64toh(m->timestamp) != USEC_INFINITY &&
|
||||
le64toh(m->not_after) != USEC_INFINITY &&
|
||||
le64toh(m->timestamp) >= le64toh(m->not_after))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Timestamps of credential are not in order, refusing.");
|
||||
|
||||
if (le32toh(m->name_size) > CREDENTIAL_NAME_MAX)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Embedded credential name too long, refusing.");
|
||||
|
||||
hs = ALIGN8(offsetof(struct metadata_credential_header, name) + le32toh(m->name_size));
|
||||
if (plaintext_size < hs)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Metadata header incomplete.");
|
||||
|
||||
if (le32toh(m->name_size) > 0) {
|
||||
_cleanup_free_ char *embedded_name = NULL;
|
||||
|
||||
if (memchr(m->name, 0, le32toh(m->name_size)))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Embedded credential name contains NUL byte, refusing.");
|
||||
|
||||
embedded_name = memdup_suffix0(m->name, le32toh(m->name_size));
|
||||
if (!embedded_name)
|
||||
return log_oom();
|
||||
|
||||
if (!credential_name_valid(embedded_name))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Embedded credential name is not valid, refusing.");
|
||||
|
||||
if (validate_name && !streq(embedded_name, validate_name)) {
|
||||
|
||||
r = getenv_bool_secure("SYSTEMD_CREDENTIAL_VALIDATE_NAME");
|
||||
if (r < 0 && r != -ENXIO)
|
||||
log_debug_errno(r, "Failed to parse $SYSTEMD_CREDENTIAL_VALIDATE_NAME: %m");
|
||||
if (r != 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Embedded credential name '%s' does not match filename '%s', refusing.", embedded_name, validate_name);
|
||||
|
||||
log_debug("Embedded credential name '%s' does not match expected name '%s', but configured to use credential anyway.", embedded_name, validate_name);
|
||||
}
|
||||
}
|
||||
|
||||
if (validate_timestamp != USEC_INFINITY) {
|
||||
if (le64toh(m->timestamp) != USEC_INFINITY && le64toh(m->timestamp) > validate_timestamp)
|
||||
log_debug("Credential timestamp is from the future, assuming clock skew.");
|
||||
|
||||
if (le64toh(m->not_after) != USEC_INFINITY && le64toh(m->not_after) < validate_timestamp) {
|
||||
|
||||
r = getenv_bool_secure("SYSTEMD_CREDENTIAL_VALIDATE_NOT_AFTER");
|
||||
if (r < 0 && r != -ENXIO)
|
||||
log_debug_errno(r, "Failed to parse $SYSTEMD_CREDENTIAL_VALIDATE_NOT_AFTER: %m");
|
||||
if (r != 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ESTALE), "Credential's time passed, refusing to use.");
|
||||
|
||||
log_debug("Credential not-after timestamp has passed, but configured to use credential anyway.");
|
||||
}
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
char *without_metadata;
|
||||
|
||||
without_metadata = memdup((uint8_t*) plaintext + hs, plaintext_size - hs);
|
||||
if (!without_metadata)
|
||||
return log_oom();
|
||||
|
||||
*ret = without_metadata;
|
||||
}
|
||||
|
||||
if (ret_size)
|
||||
*ret_size = plaintext_size - hs;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *ret_size) {
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available.");
|
||||
}
|
||||
|
||||
int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_pcr_mask, const void *input, size_t input_size, void **ret, size_t *ret_size) {
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available.");
|
||||
}
|
||||
|
||||
int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const void *input, size_t input_size, void **ret, size_t *ret_size) {
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available.");
|
||||
}
|
||||
|
||||
#endif
|
47
src/shared/creds-util.h
Normal file
47
src/shared/creds-util.h
Normal file
@ -0,0 +1,47 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "sd-id128.h"
|
||||
|
||||
#include "fd-util.h"
|
||||
#include "time-util.h"
|
||||
|
||||
#define CREDENTIAL_NAME_MAX FDNAME_MAX
|
||||
|
||||
/* Put a size limit on the individual credential */
|
||||
#define CREDENTIAL_SIZE_MAX (1024U*1024U)
|
||||
|
||||
/* Refuse to store more than 1M per service, after all this is unswappable memory. Note that for now we put
|
||||
* this to the same limit as the per-credential limit, i.e. if the user has n > 1 credentials instead of 1 it
|
||||
* won't get them more space. */
|
||||
#define CREDENTIALS_TOTAL_SIZE_MAX CREDENTIAL_SIZE_MAX
|
||||
|
||||
/* Put a size limit on encrypted credentials (which is the same as the unencrypted size plus a spacious 128K of extra
|
||||
* space for headers, IVs, exported TPM2 key material and so on. */
|
||||
#define CREDENTIAL_ENCRYPTED_SIZE_MAX (CREDENTIAL_SIZE_MAX + 128U*1024U)
|
||||
|
||||
bool credential_name_valid(const char *s);
|
||||
|
||||
int get_credentials_dir(const char **ret);
|
||||
|
||||
int read_credential(const char *name, void **ret, size_t *ret_size);
|
||||
|
||||
typedef enum CredentialSecretFlags {
|
||||
CREDENTIAL_SECRET_GENERATE = 1 << 0,
|
||||
CREDENTIAL_SECRET_WARN_NOT_ENCRYPTED = 1 << 1,
|
||||
CREDENTIAL_SECRET_FAIL_ON_TEMPORARY_FS = 1 << 2,
|
||||
} CredentialSecretFlags;
|
||||
|
||||
int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *ret_size);
|
||||
|
||||
/* The three modes we support: keyed only by on-disk key, only by TPM2 HMAC key, and by the combination of both */
|
||||
#define CRED_AES256_GCM_BY_HOST SD_ID128_MAKE(5a,1c,6a,86,df,9d,40,96,b1,d5,a6,5e,08,62,f1,9a)
|
||||
#define CRED_AES256_GCM_BY_TPM2_HMAC SD_ID128_MAKE(0c,7c,c0,7b,11,76,45,91,9c,4b,0b,ea,08,bc,20,fe)
|
||||
#define CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC SD_ID128_MAKE(93,a8,94,09,48,74,44,90,90,ca,f2,fc,93,ca,b5,53)
|
||||
|
||||
int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_pcr_mask, const void *input, size_t input_size, void **ret, size_t *ret_size);
|
||||
int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const void *input, size_t input_size, void **ret, size_t *ret_size);
|
@ -81,6 +81,8 @@ shared_sources = files('''
|
||||
coredump-util.h
|
||||
cpu-set-util.c
|
||||
cpu-set-util.h
|
||||
creds-util.c
|
||||
creds-util.h
|
||||
cryptsetup-util.c
|
||||
cryptsetup-util.h
|
||||
daemon-util.h
|
||||
|
@ -999,6 +999,25 @@ static void test_read_full_file_offset_size(void) {
|
||||
assert_se(memcmp(buf, rbuf, rbuf_size) == 0);
|
||||
rbuf = mfree(rbuf);
|
||||
|
||||
assert_se(read_full_file_full(AT_FDCWD, fn, UINT64_MAX, 128, READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &rbuf, &rbuf_size) == -E2BIG);
|
||||
assert_se(read_full_file_full(AT_FDCWD, fn, UINT64_MAX, sizeof(buf)-1, READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &rbuf, &rbuf_size) == -E2BIG);
|
||||
assert_se(read_full_file_full(AT_FDCWD, fn, UINT64_MAX, sizeof(buf), READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &rbuf, &rbuf_size) >= 0);
|
||||
assert_se(rbuf_size == sizeof(buf));
|
||||
assert_se(memcmp(buf, rbuf, rbuf_size) == 0);
|
||||
rbuf = mfree(rbuf);
|
||||
|
||||
assert_se(read_full_file_full(AT_FDCWD, fn, 47, 128, READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &rbuf, &rbuf_size) == -E2BIG);
|
||||
assert_se(read_full_file_full(AT_FDCWD, fn, 47, sizeof(buf)-47-1, READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &rbuf, &rbuf_size) == -E2BIG);
|
||||
assert_se(read_full_file_full(AT_FDCWD, fn, 47, sizeof(buf)-47, READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &rbuf, &rbuf_size) >= 0);
|
||||
assert_se(rbuf_size == sizeof(buf)-47);
|
||||
assert_se(memcmp(buf+47, rbuf, rbuf_size) == 0);
|
||||
rbuf = mfree(rbuf);
|
||||
|
||||
assert_se(read_full_file_full(AT_FDCWD, fn, UINT64_MAX, sizeof(buf)+1, READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &rbuf, &rbuf_size) >= 0);
|
||||
assert_se(rbuf_size == sizeof(buf));
|
||||
assert_se(memcmp(buf, rbuf, rbuf_size) == 0);
|
||||
rbuf = mfree(rbuf);
|
||||
|
||||
assert_se(read_full_file_full(AT_FDCWD, fn, 1234, SIZE_MAX, 0, NULL, &rbuf, &rbuf_size) >= 0);
|
||||
assert_se(rbuf_size == sizeof(buf) - 1234);
|
||||
assert_se(memcmp(buf + 1234, rbuf, rbuf_size) == 0);
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "alloc-util.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "macro.h"
|
||||
#include "random-util.h"
|
||||
#include "string-util.h"
|
||||
|
||||
static void test_hexchar(void) {
|
||||
@ -275,6 +276,37 @@ static void test_base64mem(void) {
|
||||
free(b64);
|
||||
}
|
||||
|
||||
static void test_base64mem_linebreak(void) {
|
||||
uint8_t data[4096];
|
||||
|
||||
for (size_t i = 0; i < 20; i++) {
|
||||
_cleanup_free_ char *encoded = NULL;
|
||||
_cleanup_free_ void *decoded = NULL;
|
||||
size_t decoded_size;
|
||||
uint64_t n, m;
|
||||
ssize_t l;
|
||||
|
||||
/* Try a bunch of differently sized blobs */
|
||||
n = random_u64_range(sizeof(data));
|
||||
random_bytes(data, n);
|
||||
|
||||
/* Break at various different columns */
|
||||
m = 1 + random_u64_range(n + 5);
|
||||
|
||||
l = base64mem_full(data, n, m, &encoded);
|
||||
assert_se(l >= 0);
|
||||
assert_se(encoded);
|
||||
assert_se((size_t) l == strlen(encoded));
|
||||
|
||||
assert_se(unbase64mem(encoded, SIZE_MAX, &decoded, &decoded_size) >= 0);
|
||||
assert_se(decoded_size == n);
|
||||
assert_se(memcmp(data, decoded, n) == 0);
|
||||
|
||||
for (size_t j = 0; j < (size_t) l; j++)
|
||||
assert_se((encoded[j] == '\n') == (j % (m + 1) == m));
|
||||
}
|
||||
}
|
||||
|
||||
static void test_unbase64mem_one(const char *input, const char *output, int ret) {
|
||||
_cleanup_free_ void *buffer = NULL;
|
||||
size_t size = 0;
|
||||
@ -348,6 +380,7 @@ int main(int argc, char *argv[]) {
|
||||
test_base32hexmem();
|
||||
test_unbase32hexmem();
|
||||
test_base64mem();
|
||||
test_base64mem_linebreak();
|
||||
test_unbase64mem();
|
||||
test_hexdump();
|
||||
|
||||
|
@ -1355,7 +1355,7 @@ static int fd_set_attribute(Item *item, int fd, const char *path, const struct s
|
||||
return log_error_errno(procfs_fd, "Failed to re-open '%s': %m", path);
|
||||
|
||||
unsigned previous, current;
|
||||
r = chattr_full(NULL, procfs_fd, f, item->attribute_mask, &previous, ¤t, true);
|
||||
r = chattr_full(NULL, procfs_fd, f, item->attribute_mask, &previous, ¤t, CHATTR_FALLBACK_BITWISE);
|
||||
if (r == -ENOANO)
|
||||
log_warning("Cannot set file attributes for '%s', maybe due to incompatibility in specified attributes, "
|
||||
"previous=0x%08x, current=0x%08x, expected=0x%08x, ignoring.",
|
||||
|
@ -83,6 +83,7 @@ LimitRTTIME=
|
||||
LimitSIGPENDING=
|
||||
LimitSTACK=
|
||||
LoadCredential=
|
||||
LoadCredentialEncrypted=
|
||||
LockPersonality=
|
||||
LogExtraFields=
|
||||
LogLevelMax=
|
||||
@ -159,6 +160,7 @@ SecureBits=
|
||||
SendSIGHUP=
|
||||
SendSIGKILL=
|
||||
SetCredential=
|
||||
SetCredentialEncrypted=
|
||||
Slice=
|
||||
SloppyOptions=
|
||||
SmackProcessLabel=
|
||||
|
@ -205,6 +205,7 @@ LimitRTTIME=
|
||||
LimitSIGPENDING=
|
||||
LimitSTACK=
|
||||
LoadCredential=
|
||||
LoadCredentialEncrypted=
|
||||
LockPersonality=
|
||||
LogExtraFields=
|
||||
LogLevelMax=
|
||||
@ -292,6 +293,7 @@ SecureBits=
|
||||
SendSIGHUP=
|
||||
SendSIGKILL=
|
||||
SetCredential=
|
||||
SetCredentialEncrypted=
|
||||
Slice=
|
||||
SmackProcessLabel=
|
||||
Sockets=
|
||||
|
@ -108,6 +108,7 @@ ListenSpecial=
|
||||
ListenStream=
|
||||
ListenUSBFunction=
|
||||
LoadCredential=
|
||||
LoadCredentialEncrypted=
|
||||
LockPersonality=
|
||||
LogExtraFields=
|
||||
LogLevelMax=
|
||||
@ -199,6 +200,7 @@ SendSIGHUP=
|
||||
SendSIGKILL=
|
||||
Service=
|
||||
SetCredential=
|
||||
SetCredentialEncrypted=
|
||||
Slice=
|
||||
SmackLabel=
|
||||
SmackLabelIPIn=
|
||||
|
@ -80,6 +80,7 @@ LimitRTTIME=
|
||||
LimitSIGPENDING=
|
||||
LimitSTACK=
|
||||
LoadCredential=
|
||||
LoadCredentialEncrypted=
|
||||
LockPersonality=
|
||||
LogExtraFields=
|
||||
LogLevelMax=
|
||||
@ -156,6 +157,7 @@ SecureBits=
|
||||
SendSIGHUP=
|
||||
SendSIGKILL=
|
||||
SetCredential=
|
||||
SetCredentialEncrypted=
|
||||
Slice=
|
||||
SmackProcessLabel=
|
||||
SocketBindAllow=
|
||||
|
@ -27,6 +27,30 @@ systemd-run -p LoadCredential=passwd:/etc/passwd \
|
||||
rm '${CREDENTIALS_DIRECTORY}/passwd' \
|
||||
&& { echo 'unexpected success'; exit 1; }
|
||||
|
||||
# Now test encrypted credentials (only supported when built with OpenSSL though)
|
||||
|
||||
if systemctl --version | grep -q -- +OPENSSL ; then
|
||||
echo -n $RANDOM >/tmp/test-54-plaintext
|
||||
systemd-creds encrypt --name=test-54 /tmp/test-54-plaintext /tmp/test-54-ciphertext
|
||||
systemd-creds decrypt --name=test-54 /tmp/test-54-ciphertext | cmp /tmp/test-54-plaintext
|
||||
|
||||
systemd-run -p LoadCredentialEncrypted=test-54:/tmp/test-54-ciphertext \
|
||||
--wait \
|
||||
--pipe \
|
||||
cat '${CREDENTIALS_DIRECTORY}/test-54' | cmp /tmp/test-54-plaintext
|
||||
|
||||
echo -n $RANDOM >/tmp/test-54-plaintext
|
||||
systemd-creds encrypt --name=test-54 /tmp/test-54-plaintext /tmp/test-54-ciphertext
|
||||
systemd-creds decrypt --name=test-54 /tmp/test-54-ciphertext | cmp /tmp/test-54-plaintext
|
||||
|
||||
systemd-run -p SetCredentialEncrypted=test-54:"`cat /tmp/test-54-ciphertext`" \
|
||||
--wait \
|
||||
--pipe \
|
||||
cat '${CREDENTIALS_DIRECTORY}/test-54' | cmp /tmp/test-54-plaintext
|
||||
|
||||
rm /tmp/test-54-plaintext /tmp/test-54-ciphertext
|
||||
fi
|
||||
|
||||
systemd-analyze log-level info
|
||||
|
||||
echo OK >/testok
|
||||
|
Loading…
Reference in New Issue
Block a user