diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
index 0afde99ee6..c0c147045e 100644
--- a/man/systemd.exec.xml
+++ b/man/systemd.exec.xml
@@ -3915,6 +3915,13 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX
LoadCredential= and LoadCredentialEncrypted= take priority over
credentials found by ImportCredential=.
+ Note that if decryption or authentication of a credential picked up as result of
+ ImportCredential= fails it will be skipped gracefully (a warning is generated, but
+ the credential will not be made available to the invoked service). This is different for those
+ configured via
+ SetCredentialEncrypted=/LoadCredentialEncrypted=, where failed
+ decryption/authentication will result in service failure.
+
diff --git a/src/core/exec-credential.c b/src/core/exec-credential.c
index f7f388c2c0..b0cc9ad068 100644
--- a/src/core/exec-credential.c
+++ b/src/core/exec-credential.c
@@ -463,7 +463,8 @@ static int maybe_decrypt_and_write_credential(
struct load_cred_args *args,
const char *id,
const char *data,
- size_t size) {
+ size_t size,
+ bool graceful) {
_cleanup_(iovec_done_erase) struct iovec plaintext = {};
size_t add;
@@ -517,8 +518,14 @@ static int maybe_decrypt_and_write_credential(
default:
assert_not_reached();
}
- if (r < 0)
+ if (r < 0) {
+ if (graceful) {
+ log_warning_errno(r, "Unable to decrypt credential '%s', skipping.", id);
+ return 0;
+ }
+
return r;
+ }
data = plaintext.iov_base;
size = plaintext.iov_len;
@@ -607,7 +614,7 @@ static int load_credential_glob(
if (r < 0)
return log_debug_errno(r, "Failed to read credential '%s': %m", *p);
- r = maybe_decrypt_and_write_credential(args, fn, data, size);
+ r = maybe_decrypt_and_write_credential(args, fn, data, size, /* graceful= */ true);
if (r < 0)
return r;
}
@@ -732,7 +739,7 @@ static int load_credential(
if (r < 0)
return log_debug_errno(r, "Failed to read credential '%s': %m", path);
- return maybe_decrypt_and_write_credential(args, id, data, size);
+ return maybe_decrypt_and_write_credential(args, id, data, size, /* graceful= */ true);
}
static int load_cred_recurse_dir_cb(
@@ -869,10 +876,11 @@ static int acquire_credentials(
args.encrypted = false;
- r = load_credential_glob(&args,
- ic,
- search_path,
- READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER);
+ r = load_credential_glob(
+ &args,
+ ic,
+ search_path,
+ READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER);
if (r < 0)
return r;
@@ -884,10 +892,11 @@ static int acquire_credentials(
args.encrypted = true;
- r = load_credential_glob(&args,
- ic,
- search_path,
- READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER|READ_FULL_FILE_UNBASE64);
+ r = load_credential_glob(
+ &args,
+ ic,
+ search_path,
+ READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER|READ_FULL_FILE_UNBASE64);
if (r < 0)
return r;
}
@@ -905,7 +914,7 @@ static int acquire_credentials(
if (errno != ENOENT)
return log_debug_errno(errno, "Failed to test if credential %s exists: %m", sc->id);
- r = maybe_decrypt_and_write_credential(&args, sc->id, sc->data, sc->size);
+ r = maybe_decrypt_and_write_credential(&args, sc->id, sc->data, sc->size, /* graceful= */ false);
if (r < 0)
return r;
}
diff --git a/src/creds/creds.c b/src/creds/creds.c
index a121d0695b..ab3beabc02 100644
--- a/src/creds/creds.c
+++ b/src/creds/creds.c
@@ -1411,7 +1411,7 @@ static int vl_method_decrypt(sd_varlink *link, sd_json_variant *parameters, sd_v
if (r == -EBADMSG)
return sd_varlink_error(link, "io.systemd.Credentials.BadFormat", NULL);
- if (r == -EREMOTE)
+ if (r == -EDESTADDRREQ)
return sd_varlink_error(link, "io.systemd.Credentials.NameMismatch", NULL);
if (r == -ESTALE)
return sd_varlink_error(link, "io.systemd.Credentials.TimeMismatch", NULL);
@@ -1419,6 +1419,16 @@ static int vl_method_decrypt(sd_varlink *link, sd_json_variant *parameters, sd_v
return sd_varlink_error(link, "io.systemd.Credentials.NoSuchUser", NULL);
if (r == -EMEDIUMTYPE)
return sd_varlink_error(link, "io.systemd.Credentials.BadScope", NULL);
+ if (r == -EHOSTDOWN)
+ return sd_varlink_error(link, "io.systemd.Credentials.CantFindPCRSignature", NULL);
+ if (r == -EHWPOISON)
+ return sd_varlink_error(link, "io.systemd.Credentials.NullKeyNotAllowed", NULL);
+ if (r == -EREMOTE)
+ return sd_varlink_error(link, "io.systemd.Credentials.KeyBelongsToOtherTPM", NULL);
+ if (r == -ENOLCK)
+ return sd_varlink_error(link, "io.systemd.Credentials.TPMInDictionaryLockout", NULL);
+ if (IN_SET(r, -EREMCHG, -ENOANO, -EUCLEAN, -EPERM))
+ return sd_varlink_error(link, "io.systemd.Credentials.UnexpectedPCRState", NULL);
if (r < 0)
return r;
@@ -1433,6 +1443,38 @@ static int vl_method_decrypt(sd_varlink *link, sd_json_variant *parameters, sd_v
return sd_varlink_reply(link, reply);
}
+static int vl_server(void) {
+ _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL;
+ _cleanup_hashmap_free_ Hashmap *polkit_registry = NULL;
+ int r;
+
+ /* Invocation as Varlink service */
+
+ r = varlink_server_new(
+ &varlink_server,
+ SD_VARLINK_SERVER_ACCOUNT_UID|SD_VARLINK_SERVER_INHERIT_USERDATA|SD_VARLINK_SERVER_INPUT_SENSITIVE,
+ &polkit_registry);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate Varlink server: %m");
+
+ r = sd_varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_Credentials);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add Varlink interface: %m");
+
+ r = sd_varlink_server_bind_method_many(
+ varlink_server,
+ "io.systemd.Credentials.Encrypt", vl_method_encrypt,
+ "io.systemd.Credentials.Decrypt", vl_method_decrypt);
+ if (r < 0)
+ return log_error_errno(r, "Failed to bind Varlink methods: %m");
+
+ r = sd_varlink_server_loop_auto(varlink_server);
+ if (r < 0)
+ return log_error_errno(r, "Failed to run Varlink event loop: %m");
+
+ return 0;
+}
+
static int run(int argc, char *argv[]) {
int r;
@@ -1442,38 +1484,8 @@ static int run(int argc, char *argv[]) {
if (r <= 0)
return r;
- if (arg_varlink) {
- _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL;
- _cleanup_hashmap_free_ Hashmap *polkit_registry = NULL;
-
- /* Invocation as Varlink service */
-
- r = varlink_server_new(
- &varlink_server,
- SD_VARLINK_SERVER_ACCOUNT_UID|SD_VARLINK_SERVER_INHERIT_USERDATA|SD_VARLINK_SERVER_INPUT_SENSITIVE,
- NULL);
- if (r < 0)
- return log_error_errno(r, "Failed to allocate Varlink server: %m");
-
- r = sd_varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_Credentials);
- if (r < 0)
- return log_error_errno(r, "Failed to add Varlink interface: %m");
-
- r = sd_varlink_server_bind_method_many(
- varlink_server,
- "io.systemd.Credentials.Encrypt", vl_method_encrypt,
- "io.systemd.Credentials.Decrypt", vl_method_decrypt);
- if (r < 0)
- return log_error_errno(r, "Failed to bind Varlink methods: %m");
-
- sd_varlink_server_set_userdata(varlink_server, &polkit_registry);
-
- r = sd_varlink_server_loop_auto(varlink_server);
- if (r < 0)
- return log_error_errno(r, "Failed to run Varlink event loop: %m");
-
- return 0;
- }
+ if (arg_varlink)
+ return vl_server();
return creds_main(argc, argv);
}
diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c
index 9426f00394..dc337dc122 100644
--- a/src/shared/creds-util.c
+++ b/src/shared/creds-util.c
@@ -1201,6 +1201,19 @@ int decrypt_credential_and_warn(
assert(iovec_is_valid(input));
assert(ret);
+ /* Relevant error codes:
+ *
+ * -EBADMSG → Corrupted file
+ * -EOPNOTSUPP → Unsupported file type (could be: requires TPM but we have no TPM)
+ * -EHOSTDOWN → Need PCR signature file, but couldn't find it
+ * -EHWPOISON → Attempt to decode NULL key (and CREDENTIAL_ALLOW_NULL is off), but the system has a TPM and SecureBoot is on
+ * -EMEDIUMTYPE → File has unexpected scope, i.e. user-scoped credential is attempted to be unlocked in system scope, or vice versa
+ * -EDESTADDRREQ → Credential is incorrectly named (i.e. the authenticated name does not match the actual name)
+ * -ESTALE → Credential's valdity has passed
+ * -ESRCH → User specified for scope does not exist on this system
+ *
+ * (plus the various error codes tpm2_unseal() returns) */
+
h = (struct encrypted_credential_header*) input->iov_base;
/* The ID must fit in, for the current and all future formats */
@@ -1218,8 +1231,10 @@ int decrypt_credential_and_warn(
if (with_tpm2_pk) {
r = tpm2_load_pcr_signature(tpm2_signature_path, &signature_json);
+ if (r == -ENOENT)
+ return log_error_errno(SYNTHETIC_ERRNO(EHOSTDOWN), "Couldn't find PCR signature file: %m");
if (r < 0)
- return log_error_errno(r, "Failed to load pcr signature: %m");
+ return log_error_errno(r, "Failed to load PCR signature: %m");
}
if (with_null && !FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL)) {
@@ -1234,7 +1249,7 @@ int decrypt_credential_and_warn(
if (efi_has_tpm2()) {
if (is_efi_secure_boot())
- return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
+ return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON),
"Credential uses fixed key for fallback use when TPM2 is absent — but TPM2 is present, and SecureBoot is enabled, refusing.");
log_warning("Credential uses fixed key for use when TPM2 is absent, but TPM2 is present! Accepting anyway, since SecureBoot is disabled.");
@@ -1354,7 +1369,7 @@ int decrypt_credential_and_warn(
/* srk= */ NULL,
&tpm2_key);
if (r == -EREMOTE)
- return log_error_errno(r, "TPM key integrity check failed. Key enrolled in superblock most likely does not belong to this TPM.");
+ return log_error_errno(r, "TPM key integrity check failed. Key most likely does not belong to this TPM.");
if (ERRNO_IS_NEG_TPM2_UNSEAL_BAD_PCR(r))
return log_error_errno(r, "TPM policy does not match current system state. Either system has been tempered with or policy out-of-date: %m");
if (r < 0)
@@ -1486,7 +1501,7 @@ int decrypt_credential_and_warn(
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);
+ return log_error_errno(SYNTHETIC_ERRNO(EDESTADDRREQ), "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);
}
@@ -1637,16 +1652,26 @@ int ipc_decrypt_credential(const char *validate_name, usec_t validate_timestamp,
if (r < 0)
return log_error_errno(r, "Failed to call Decrypt() varlink call.");
if (!isempty(error_id)) {
- if (streq(error_id, "io.systemd.Credentials.BadFormat"))
- return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Bad credential format.");
- if (streq(error_id, "io.systemd.Credentials.NameMismatch"))
- return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Name in credential doesn't match expectations.");
- if (streq(error_id, "io.systemd.Credentials.TimeMismatch"))
- return log_error_errno(SYNTHETIC_ERRNO(ESTALE), "Outside of credential validity time window.");
- if (streq(error_id, "io.systemd.Credentials.NoSuchUser"))
- return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "No such user.");
- if (streq(error_id, "io.systemd.Credentials.BadScope"))
- return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Scope mismtach.");
+ static struct {
+ const char *id;
+ int errnum;
+ const char *msg;
+ } table[] = {
+ { "io.systemd.Credentials.BadFormat", EBADMSG, "Bad credential format." },
+ { "io.systemd.Credentials.NameMismatch", EDESTADDRREQ, "Name in credential doesn't match expectations." },
+ { "io.systemd.Credentials.TimeMismatch", ESTALE, "Outside of credential validity time window." },
+ { "io.systemd.Credentials.NoSuchUser", ESRCH, "No such user." },
+ { "io.systemd.Credentials.BadScope", EMEDIUMTYPE, "Scope mismatch." },
+ { "io.systemd.Credentials.CantFindPCRSignature", EHOSTDOWN, "PCR signature required for decryption, but could not be found." },
+ { "io.systemd.Credentials.NullKeyNotAllowed", EHWPOISON, "The key was encrypted with a null key, but that's now allowed during decryption." },
+ { "io.systemd.Credentials.KeyBelongsToOtherTPM", EREMOTE, "The TPM integrity check for this key failed, key probably belongs to another TPM, or was corrupted." },
+ { "io.systemd.Credentials.TPMInDictionaryLockout", ENOLCK, "The TPM is in dictionary lockout mode, cannot operate." },
+ { "io.systemd.Credentials.UnexpectedPCRState" , EUCLEAN, "Unexpected TPM PCR state of the system." },
+ };
+
+ FOREACH_ELEMENT(i, table)
+ if (streq(i->id, error_id))
+ return log_error_errno(SYNTHETIC_ERRNO(i->errnum), "%s", i->msg);
return log_error_errno(sd_varlink_error_to_errno(error_id, reply), "Failed to decrypt: %s", error_id);
}
diff --git a/src/shared/varlink-io.systemd.Credentials.c b/src/shared/varlink-io.systemd.Credentials.c
index 69e36cda91..ab99adefe5 100644
--- a/src/shared/varlink-io.systemd.Credentials.c
+++ b/src/shared/varlink-io.systemd.Credentials.c
@@ -44,6 +44,11 @@ static SD_VARLINK_DEFINE_ERROR(NameMismatch);
static SD_VARLINK_DEFINE_ERROR(TimeMismatch);
static SD_VARLINK_DEFINE_ERROR(NoSuchUser);
static SD_VARLINK_DEFINE_ERROR(BadScope);
+static SD_VARLINK_DEFINE_ERROR(CantFindPCRSignature);
+static SD_VARLINK_DEFINE_ERROR(NullKeyNotAllowed);
+static SD_VARLINK_DEFINE_ERROR(KeyBelongsToOtherTPM);
+static SD_VARLINK_DEFINE_ERROR(TPMInDictionaryLockout);
+static SD_VARLINK_DEFINE_ERROR(UnexpectedPCRState);
SD_VARLINK_DEFINE_INTERFACE(
io_systemd_Credentials,
@@ -62,4 +67,14 @@ SD_VARLINK_DEFINE_INTERFACE(
SD_VARLINK_SYMBOL_COMMENT("The specified user does not exist."),
&vl_error_NoSuchUser,
SD_VARLINK_SYMBOL_COMMENT("The credential does not match the selected scope."),
- &vl_error_BadScope);
+ &vl_error_BadScope,
+ SD_VARLINK_SYMBOL_COMMENT("PCR signature required for decryption, but not found."),
+ &vl_error_CantFindPCRSignature,
+ SD_VARLINK_SYMBOL_COMMENT("The key was encrypted with a null key, but that's now allowed during decryption."),
+ &vl_error_NullKeyNotAllowed,
+ SD_VARLINK_SYMBOL_COMMENT("The TPM integrity check for this key failed, key probably belongs to another TPM, or was corrupted."),
+ &vl_error_KeyBelongsToOtherTPM,
+ SD_VARLINK_SYMBOL_COMMENT("The TPM is in dictionary lockout mode, cannot operate."),
+ &vl_error_TPMInDictionaryLockout,
+ SD_VARLINK_SYMBOL_COMMENT("Unexpected TPM PCR state of the system."),
+ &vl_error_UnexpectedPCRState);
diff --git a/src/test/test-creds.c b/src/test/test-creds.c
index 070b02fa90..384c10c70e 100644
--- a/src/test/test-creds.c
+++ b/src/test/test-creds.c
@@ -164,7 +164,7 @@ static void test_encrypt_decrypt_with(sd_id128_t mode, uid_t uid) {
&encrypted,
CREDENTIAL_ALLOW_NULL,
&decrypted);
- ASSERT_ERROR(r, EREMOTE); /* name didn't match */
+ ASSERT_ERROR(r, EDESTADDRREQ); /* name didn't match */
r = decrypt_credential_and_warn(
"foo",