Skip to content

Conversation

@raboof
Copy link
Contributor

@raboof raboof commented Mar 6, 2025

Summary

Before this patch, when decrypting a value without using a password, it would call decryptWithoutSecret with the system secret as password. When this fails, it would retry with an empty string as password.

This has the practical disadvantage that it can lead to confusing error messages. For example, when using the TOTP app, when the system secret is misconfigured, the first invocation will throw a sensible HMAC does not match. error, but then it is retried and the retry throws a Hash_hkdf(): Argument #2 ($key) cannot be empty error causing confusion (e.g.
https://help.nextcloud.com/t/hash-hkdf-argument-2-key-cannot-be-empty/192556).

Of course this fallback to using an empty string is likely part of some sort of graceful migration from the days when the secret could be empty (e.g. #34012, #31499, /cc @juliusknorr ).

However, taking a wider perspective, such 'fallback logic' in security-critical areas makes things more complex, which is a risk. It's not quite the same scenario, but Heartbleed does come to mind.

For this reason, rather than a 'surgical' improvement for the particular case encountered above (increasing complexity further), I think it'd be worth it to start considering removing this fallback entirely (perhaps in the next major version?) - hence this conversation-starter PR.

I'll keep this in draft for now because it needs a unit test, but otherwise I'd love to start the review/conversation.

TODO

  • add a unit test
  • (perhaps in a follow-up PR) remove the fallback in more places

Checklist

Before this patch, when decrypting a value without using a password,
it would call `decryptWithoutSecret` with the system `secret` as
`password`. When this fails, it would retry with an empty string as
`password`.

This has the practical disadvantage that it can lead to confusing
error messages. For example, when using the TOTP app, when the system
`secret` is misconfigured, the first invocation will throw a sensible
`HMAC does not match.` error, but then it is retried and the retry
throws a `Hash_hkdf(): Argument nextcloud#2 ($key) cannot be empty` error
causing confusion (e.g.
https://help.nextcloud.com/t/hash-hkdf-argument-2-key-cannot-be-empty/192556).

Of course this fallback to using an empty string is likely part of
some sort of graceful migration from the days when the secret could
be empty (e.g. nextcloud#34012,
nextcloud#31499).

However, taking a wider perspective, such 'fallback logic' in
security-critical areas makes things more complex, which is a risk.
It's not quite the same scenario, but Heartbleed does come to mind.

For this reason, rather than a 'surgical' improvement for the particular
case encountered above (increasing complexity further), I think it'd be
worth to start considering removing this fallback entirely
(perhaps in v32.0.0?) - hence this conversation-starter PR.

Signed-off-by: Arnout Engelen <[email protected]>
@raboof raboof requested a review from a team as a code owner March 6, 2025 09:23
@raboof raboof requested review from come-nc, sorbaugh and yemkareems and removed request for a team March 6, 2025 09:23
@raboof raboof marked this pull request as draft March 6, 2025 09:23
@joshtrichards
Copy link
Member

Related: #46176 (I haven't had a chance to pick it back up).

@raboof
Copy link
Contributor Author

raboof commented Mar 6, 2025

Related: #46176 (I haven't had a chance to pick it back up).

Ah, missed that, thanks for adding the link! I think both PRs still make sense: your 'surgical' improvement (that can be backported) and possibly my more 'blunt' one (for the next major version).

@come-nc
Copy link
Contributor

come-nc commented Mar 6, 2025

If I recall correctly the problem is that it’s not possible for an instance to change its secret I think, so if an empty secret was used you are stuck with it, no? Or more likely you can set the secret, but it will only be used for new data.

If there is an upgrade path from a 31 with an empty secret to a 32 with a secret correctly set then I’m perfectly fine with dropping the workaround. But that means making sure all data using the empty secret is migrated to the new one.

The version in #46176 is way safer in my opinion. My comments in there still stand though.

@github-actions
Copy link
Contributor

Hello there,
Thank you so much for taking the time and effort to create a pull request to our Nextcloud project.

We hope that the review process is going smooth and is helpful for you. We want to ensure your pull request is reviewed to your satisfaction. If you have a moment, our community management team would very much appreciate your feedback on your experience with this PR review process.

Your feedback is valuable to us as we continuously strive to improve our community developer experience. Please take a moment to complete our short survey by clicking on the following link: https://cloud.nextcloud.com/apps/forms/s/i9Ago4EQRZ7TWxjfmeEpPkf6

Thank you for contributing to Nextcloud and we hope to hear from you soon!

(If you believe you should not receive this message, you can add yourself to the blocklist.)

@raboof
Copy link
Contributor Author

raboof commented Aug 1, 2025

If there is an upgrade path from a 31 with an empty secret to a 32 with a secret correctly set then I’m perfectly fine with dropping the workaround. But that means making sure all data using the empty secret is migrated to the new one.

Realistically I think properly analyzing that is more than I have time for right now, so I'll close this (even though keeping such 'fallback' code around in security-critical areas does still make me uneasy :) )

@raboof raboof closed this Aug 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants