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",