docker-mailserver / docker-mailserver

Production-ready fullstack but simple mail server (SMTP, IMAP, LDAP, Antispam, Antivirus, etc.) running inside a container.
https://docker-mailserver.github.io/docker-mailserver/latest/
MIT License
14.18k stars 1.78k forks source link

Encryption with user keys #2058

Closed NorseGaud closed 1 year ago

NorseGaud commented 3 years ago

Opening this to hopefully get some pointers from the community on how best to implement per-user encryption vs the global key/encryption method detailed in the dovecot documentation. Once I figure this out I'll do a PR to document it.

Here are the docs for the user encryption keys: https://doc.dovecot.org/configuration_manual/mail_crypt_plugin/#encrypted-user-keys

The recommendation is to place the following into your dovecot config:

mail_attribute_dict = file:%h/Maildir/dovecot-attributes
mail_plugins = $mail_plugins mail_crypt

plugin {
  mail_crypt_curve = secp521r1
  mail_crypt_save_version = 2
  mail_crypt_require_encrypted_user_key = yes
}

This is easy, but then they recommend the following:

The password that is used to decrypt the users master/private key, must be provided via password query:

File: /etc/dovecot/dovecot-sql.conf.ext

password_query = SELECT \ email as user, password, \ '%w' AS userdb_mail_crypt_private_password \ FROM virtual_users WHERE email='%u';

As far as I know the docker container doesn't use a DB of any kind. Is that right?

And if so, does anyone recommend how best to achieve this?

NorseGaud commented 3 years ago

@marhmm : here is the new thread for you to track

marhmm commented 3 years ago

Thanks for opening this issue! I tried to implement it too but I got stuck at the same step about the missing database. There are some threads on the dovecot mailing list about folder encryption without a sql database but I wasn't able to reproduce it. Maybe someone else has more luck or a better understanding: https://dovecot.org/pipermail/dovecot/2020-August/119598.html

polarathene commented 3 years ago

As far as I know the docker container doesn't use a DB of any kind. Is that right?

Not afaik, but I assume you could provide a separate container of your choice that does to connect to since this is a explicit config change, users should be able to follow along documentation well enough?


x25519, ed25519 and ed448 are generally more preferred for curve choice unless required to use only curves accepted by NIST for example. It's unclear if these are supported though, even if the provider openssl offers them, since from what I understand, dovecot is generating the keys on-demand per user/folder itself internally rather than being provided them from a user.


This seems to suggest the db is only required for password protecting / encrypting generated keys:

This will turn on automatic key creation for folders and users. NOTE that the keys won't be encrypted in this scenario. To encrypt keys you have few choices. You can use a static password, static ECC key, per-user password, per-user ECC key, per-user password protected ECC key.

_With the per-user model, the easiest, if you don't let users change password could be adding into userdb or passdb, mail_crypt_private_password = %{sha512:password}_ If you want users to be able to change passwords, you either need to manage the encryption passwords in user database, or call doveadm mailbox cryptokey password during password change.

and few messages in that discussion later:

In managed mode, where no global key(s) are provided, dovecot will generate a user-key and folder keys. The folders keys are encrypted using user key. _User key is optionally encrypted with either password or some other key (mail_crypt_private_password / mail_crypt_private_key)._

The responses there also suggest that key management may be a hassle if user needs to change password etc.


Considering the feature being discussed, what is the actual merit for per user?

TLS provides encryption in transit (kinda.. but may depend on trusting third-parties involved when spanning multiple nodes), then it's decrypted and we have it in plain-text, same as passwords. An attacker could take advantage of either option there to gain access to data as it arrives or reading the data at rest? So is there any major benefit to per user encrypted keys?

The data is encrypted at rest, if someone compromises the server and could decrypt the data via a global key approach, they can technically do the same with user encrypted keys. If a poor password was chosen by the user, the security strength isn't relevant either.

Global key seems sufficient? If a user wants their communication data to be protected better they should probably use PGP with their peers?


And if so, does anyone recommend how best to achieve this?

That said, this request was brought up a while back and earlier this year someone shared a solution to get it working with docker-mailserver without a DB: https://github.com/docker-mailserver/docker-mailserver/issues/1171#issuecomment-805969981

We were going through a transition for our documentation at the time so they never contributed docs for it, but I assume that's enough for someone to go by :)

NorseGaud commented 3 years ago

Thanks Brennan! (@polarathene)

I'd love to keep this DB-free for now. That's a lot of added complexity and one could argue another attack vector.

then it's decrypted and we have it in plain-text, same as passwords

I'm definitely no security guy, so I apologize if this is way off, but I thought SHA512 was a one-way compression/encryption? I know postfix-accounts.cf stores the passwords in SHA512, but could those really directly be used to exploit the user's key and decrypt their mail? I definitely don't see plain-text passwords anywhere on my own server.

Regardless, I believe this becomes problematic as all currently encrypted mail with user-key-1 becomes inaccessible over IMAP when we change the password and generate user-key-2... Need to test a lot of this stuff once I get it working.

I guess we could re-encrypt all mail maybe with a script? The user could submit their old password anytime they try to set a new one? I dunno... Let's see if DavyLandman replies in the older thread so I can get a better idea of how to get it working so I can then test it on my own mailserver and see where the problems are.

NorseGaud commented 3 years ago

@marhmm , Davy was able to get back to me about his config: https://github.com/docker-mailserver/docker-mailserver/issues/1171#issuecomment-874832472

I'll be able to try this out later in the week but figured I'd mention it to you just in case you could get to it sooner and confirm it's at least functional. I'll follow up in this thread with what I find.

polarathene commented 3 years ago

TL;DR

Apologies for verbosity, here's a quick summary:


I know postfix-accounts.cf stores the passwords in SHA512, but could those really directly be used to exploit the user's key and decrypt their mail? I definitely don't see plain-text passwords anywhere on my own server.

Response I haven't looked into how the users auth flow is with the software used by `docker-mailserver`, but it's not uncommon to rely on TLS only for sending a users password over the network, then it's hashed on the server and compared to a database entry to verify. I think some of the mail auth options allow hashing from the client first, which would be usually be preferable IIRC. With regular web services, often what would happen is the user password database would get leaked via some exploit, then the attacker would have all the account passwords in hashed form. These days good security uses slow hashes (_eg bcrypt, scrypt, argon2id_) and these utilize salts which help prevent the same input password being hashed to the same value stored (_thus identifying the password of one user won't immediately identify anyone with the same password since hashes are different_). We use SHA512-crypt here I think which is default. It's not as good as bcrypt but it is NIST approved which is the general reason it's standard AFAIK, although new distros have lately been adopting yescrypt (a variant of scrypt that uses primitives which are NIST approved IIRC). Might need to look into that further at some point. Main issue with SHA512-crypt is it's very easy/fast to compute parallelized, better algorithms tend to favor requiring more resources than CPU alone to reduce the scalability of an attacker, eg with more cache memory which raises the "cost" of an attack. Anyway, assuming an attacker (which could be a malicious sysadmin who would definitely have more control) hasn't compromised the server and only managed to get a data leak of the password hashes, they would then run it through some software like Hashcat and do an offline attack with capable hardware and strategies to optimally guess original passwords and generate the same hashes that were stored. For low entropy passwords, they'll have those fairly easily, while stronger ones won't be bothered with much unless the attacker is targeting a specific individual of value. That's how you "hack" someones password. SHA512 is one-way and effectively impossible if the input entropy to hash is high enough, otherwise just a matter of time. To be clear, it's not encrypted, you cannot decrypt the value, only re-create it from an input that outputs the same hash (more than one value will, but the keyspace is so large it's statistically improbable to find another input that matches a specific target hash).

all currently encrypted mail with user-key-1 becomes inaccessible over IMAP when we change the password and generate user-key-2

Response My understanding is that the users data has it's own key for encryption on files generated by the plugin (using openssl), and that key is then optionally encrypted itself, eg with a user password. That then allows you to decrypt the key, and encrypt it again with a new password, thus you can still decrypt the same encrypted mail data as it's encryption key isn't ever changing, just the key itself being encrypted with a new password/key. I remember when I first learned about that it took a while to grok :sweat_smile: But you'd need some utility or documentation about this, and if any web interface or API (_there is an open issue about someone specifically wanting to contribute an API service for changing passwords via their frontends_), as these would need to do the process described above to transfer the encryption key, otherwise it'd fail because the new password doesn't match the older one which is still used to decrypt the mail data encryption key. As the dovecot docs mention, it provides a CLI tool for updating the password IIRC, so you just need to ensure that is invoked as part of the password change process. I believe we have a script that has a command for changing passwords explicitly, so it'd probably become a part of that? I've not looked at what we're currently doing, if no encryption at rest is offered yet, you may still want to offer the global key option. Or the one without password protection for individual user folder keys, those wouldn't require special key management like described above (_I think it has to be done for each folder having individual keys all encrypted with the same user password, that's because the encrypted key could be a shared one for multiple users to access a group folder, eg work related_).

Potentially misunderstood security benefit of password protected keys I still feel the user wanting such a feature may have misunderstood the implied security benefit. It's perhaps more secure, but if the server has been compromised to the point that the keys could be accessed anyway, I think it's possible that the attacker could very well get the required password, at least for some users in that situation. Remember that in such a situation, they would presumably also have access to the private key for TLS, that can allow them to eaves drop on login auth if the password is only relying on TLS for protecting it during network transit. It would not be the only attack vector though, depending how much control they have on the server. A user that really wants data secured should not trust the server (_or any other potential third-party nodes mail will go through in transit_) and actively encrypt / decrypt from the client instead. Encrypting mail on the server at rest is still good, if that data was ever leaked, it should be impossible to decrypt without also having access to the key. Encrypting the key is still useful if the attacker is only able to access files and transfer that, but not access / modify anything else.
NorseGaud commented 3 years ago

@polarathene, that was massively helpful in clarifying the feature for me. I may have misread, but I didn't clearly understand that the per user keys are only password protected. I was definitely off target...

Also, thank you SO MUCH for the thorough breakdown and advice. That was a lot to type and I, as well as everyone who will stumble on it, recognize that and appreciate you for it.

polarathene commented 3 years ago

@NorseGaud You're welcome :) Glad it is insightful!

I may have misread, but I didn't clearly understand that the per user keys are only password protected.

I'm a little foggy on the subject without digging through my notes on the subject, but my understanding of this feature is roughly:

MailData(KeyA(KeyB)):

So to clarify, that's:

Not sure if that helps explain it any better (and there's a chance I'm mistaken with what's going on, I lack the time atm to verify).


More verbose details:

The password protection (or any other form of input that may be supported), is what enables you to better protect the real encryption key, by encrypting that key itself since we're storing it on the server (but not the required input such as a password).

That key (KeyA) is generated by dovecot AFAIK, and so is quite secure being randomly seeded (eg if it's 256-bit, to guess every number is close to the amount of estimated atoms in the observable universe... that's a big number 10^80, aka 1 followed by 80 zeroes.). Even 128-bit of entropy would be very secure unless some major breakthrough happens, it'd be more affordable/possible to take alternative methods that don't require attempting that however.

KeyA is highly likely to be far more stronger than most passwords (unless it's randomly generated characters perhaps). KeyB is only providing the (potentially weak) additional security to the user by using secret they provide, so that if someone stole MailData and KeyA it would not be possible to decrypt the data without decrypting KeyA first.

Without that protection, someone could get the files from a leak or physical access to the hard drive (assuming the filesystem itself isn't encrypted) and just use a unencrypted KeyA. That's mostly what you're adding the extra hassle of key management for to protect against AFAIK.

The user that cares about this extra measure does not trust the server, but they trust it enough to not bother with encrypting client side, perhaps due to those methods not being as flexible to communicate with other users on other mail platforms who may be less tech-savvy. The user may not be aware of how SMTP can be insecure in transit even if TLS is established, since unlike the web it's not a direct client to server exchange with MTA nodes.

DavyLandman commented 3 years ago

x25519, ed25519 and ed448 are generally more preferred for curve choice unless required to use only curves accepted by NIST for example. It's unclear if these are supported though, even if the provider openssl offers them, since from what I understand, dovecot is generating the keys on-demand per user/folder itself internally rather than being provided them from a user.

I looked into this as well, but since x25519 (and x448) is limited to a specific curve, openssl is exposing it differently. See here: https://github.com/openssl/openssl/issues/8880. Dovecot picked the API that supports generic public-private key algorithms, which sadly rules out x25519 and friends.

That said, this request was brought up a while back and earlier this year someone shared a solution to get it working with docker-mailserver without a DB: #1171 (comment)

We were going through a transition for our documentation at the time so they never contributed docs for it, but I assume that's enough for someone to go by :)

Sorry about not picking it up more, I got a bit side-railed with other stuff happening this year...

I'm glad that @NorseGaud has time to try and integrate it better.

DavyLandman commented 3 years ago

@polarathene I've looked into their implementation of the crypt-mail, and there are two approaches:

Indeed, changing your password requires re-encrypting the private key, there is an extra tool for that, that you have to call, so that would need to be integrated in the docker-mailserver tooling.

But apart from not being able to pick newer curves, the design is quite nice, and has reasonable protection against hosting your mail server on a public cloud.

polarathene commented 3 years ago

But you aren't safe from online attacks, as the keys are always in memory.

Isn't that the case with typical TLS deployment too?

If the attacker has access to private key, while forward-secrecy cipher suites prevent decrypting previous sessions, it could enable the attacker to MitM new auth sessions and get the user password to decrypt the mail data?

  • The private key is encrypted with the password of the user.

This is optional for this method AFAIK?

Although you did identify that using a SQL database isn't required to support enabling password protection. It's still technically a third option?

For existing deployments, should documentation be provided on how to go about encrypting existing mail data of the users? Or does documentation need to clarify feature support that mail data prior to enabling the feature remains unencrypted?

DavyLandman commented 3 years ago

Isn't that the case with typical TLS deployment too?

If the attacker has access to private key, while forward-secrecy cipher suites prevent decrypting previous sessions, it could enable the attacker to MitM new auth sessions and get the user password to decrypt the mail data?

Yes, true, TLS protects the transport of the data, not the at-rest-data. If the attacker is at your data center, it's quite hard. So if you have MitM on the auth session (which is not really MitM anymore since they are on your server already), then yes, not much to do about that.

  • The private key is encrypted with the password of the user.

This is optional for this method AFAIK?

If you don't encrypt the private key, there is no real security benefit with regards to the single encryption key for all mailboxes.

Although you did identify that using a SQL database isn't required to support enabling password protection. It's still technically a third option?

The default from the examples is with using a DB, all I did was figure out how to inject a similar preprocessing step into a file-based setup (I ended up asking on the dovecot mailing list for some pointers)

For existing deployments, should documentation be provided on how to go about encrypting existing mail data of the users? Or does documentation need to clarify feature support that mail data prior to enabling the feature remains unencrypted?

No idea about migrating existing data, I think they have something for that in dovecot, that old data is just flagged ast "not-encrypted". so it should handle that transparently, but don't pin me down on that ;)

NorseGaud commented 3 years ago

Well, this is odd... I had the global key enabled, switched to using the user key method, restarted everything and I can still read old emails encrypted by the global key...

The same for if I create one user key using mail_attribute_dict = file:%h/.tmp/attrs, send/receive a few emails, and then change the path to mail_attribute_dict = file:%h/.tmp2/attrs and generate a brand new user key.

Does Dovecot have some weird caching? I can't imagine it's my client...

polarathene commented 3 years ago

Does Dovecot have some weird caching? I can't imagine it's my client...

Are you inspecting the config changes within the container? eg using docker exec to get a shell inside of the running container.

I recall that if you use docker-compose up and then use ctrl+c and docker-compose up again after modifying your config, it's not equivalent to having docker-compose down before starting the container again.

For me that meant some config state wasn't immutable even though it was internal image state nothing from a mounted volume, which prevented a sed replacement in the startup shell scripts from functioning properly on the target config. More of a docker container cache gotcha really.

NorseGaud commented 3 years ago

Does Dovecot have some weird caching? I can't imagine it's my client...

Are you inspecting the config changes within the container? eg using docker exec to get a shell inside of the running container.

I recall that if you use docker-compose up and then use ctrl+c and docker-compose up again after modifying your config, it's not equivalent to having docker-compose down before starting the container again.

For me that meant some config state wasn't immutable even though it was internal image state nothing from a mounted volume, which prevented a sed replacement in the startup shell scripts from functioning properly on the target config. More of a docker container cache gotcha really.

Yep, I use docker-compose up -d and down when restarting. I'll keep poking at it. Maybe there is an obvious explanation that I'm missing.

Also, update on docs, scripts, etc: things are looking good :)

DavyLandman commented 3 years ago

docker creates anonymous volumes for mountpoints that you haven't explicitly mapped. After restarting the container (if it has the same name) it will reuse that volume.

If you want to avoid this, do down -v (or docker system prune --volumes to clear them up.)

NorseGaud commented 3 years ago

docker creates anonymous volumes for mountpoints that you haven't explicitly mapped. After restarting the container (if it has the same name) it will reuse that volume.

If you want to avoid this, do down -v (or docker system prune --volumes to clear them up.)

Is that necessary even with my entire maildata folder on a mounted volume? The keys are stored in there as far as I can tell.

DavyLandman commented 3 years ago

Is that necessary even with my entire maildata folder on a mounted volume? The keys are stored in there as far as I can tell.

I don't know about your compose setup in relationship to the Dockerfile. Just wanted to point out a part of docker where it's more statefull than most people expect.

docker system prune -a --volumes is quite good at clearing off any remains.

NorseGaud commented 3 years ago

This issue has become stale because it has been open for 20 days without activity. Remove the label and comment or this issue will be closed in 10 days.

This isn't stale, you're stale!

polarathene commented 2 years ago

I have adapted the WIP work on this feature to work with the current DMS release, however I have noticed that the encryption key was only being protected with a SHA256 hash of the users password.

I don't like this as it allows for an attacker with access to storage to more cheaply attack decrypting mail to get the users login password (single SHA256 is much faster than a slow hash algorithm used for verification storage of user login passwords).

Looking at the Dovecot docs with the SQL example, it's presumably querying a DB for the password hash, which we could use instead (postfix-accounts.cf), but likewise, anyone with read access to our storage can just use that directly as input which would defeat the purpose of choosing it over a global key.

Thus at present, the benefits of per-user encryption keys seems misleading?


I've further looked into options available, the plugin does support PKCS5 / PBKDF2 with SHA256 (user salt), but this is incompatible with the doveadm pw utility PBKDF2 SHA1 (random salt).

I'm not aware of a way to tell Dovecot to use an external method to compute the hashed password for decrypting the key, so we'd probably want to use the PKCS5 support provided - but we would need to generate a compatible hash for generating/updating the key in addmailuser / updatemailuser.

I suppose we could use the lua passdb driver, but that doesn't seem simpler (one benefit might be that we could implement a better KDF than PBKDF2 at least).

DavyLandman commented 2 years ago

@polarathene first of, thank you for picking this up! The per user encryption doesn't work as you describe (since I agree that would be bad).

It works like this:

This means that only while a user has an open connection to the server the decryption key is in memory and on disk the contents are always encrypted.

Switching away from sha256 would be nice, but that should be compatible with the interpolation syntax available in dovecot. As that is being used to transform the password to key.

Personally I think the available cipher suite is more problematic than the sha256 key.

Note, to decrypt the private key you would have to generate random sha256 strings. So I'm not so sure it would be that easy to break.

polarathene commented 2 years ago
  • encrypt the private key with the sha256 hash of the user password

This is my point...

Currently, the user without any encryption-at-rest involved will authenticate to retrieve mail via IMAP. The plaintext password is sent over TLS to Dovecot, and Dovecot will perform a lookup in the PassDB (we use the passwd-file driver)to know what Password Scheme to use (or if no hint was stored, fallback to the configured default that we've configured as SHA512-CRYPT), and process the users password with that Password Scheme to authenticate.

You probably knew all that, but this is where my point is. The computation required for the plaintext password through the Password Scheme is much more demanding on resources than a single SHA256 hash of the plaintext is.

The status of the active WIP PR, is the plaintext only went through a single SHA256 pass to derive the key to decrypt the private key used to decrypt the mail (encrypted via the public key).


If the concern was about protecting the content on the disk, should a breach occur, then the attacker will have an easy time against any weak entropy passwords no? All that is required is to send an email to the user, as the metadata is not encrypted and doveadm can be used to query the mail for that user by the subject or other metadata. Only the mail body is encrypted.

The private key could just as well be encrypted with a plaintext password in this case as the SHA256 hash isn't adding any real security. Once the mail is decrypted, the password is acquired, and that may be more valuable than the mail itself. My concern is with weakening the security of users actual accounts/passwords, by an admin enabling the mail encryption feature thinking the per-user key is more secure than the global encryption key. Under this context I don't think the per-user key is beneficial.


Switching away from sha256 would be nice, but that should be compatible with the interpolation syntax available in dovecot. As that is being used to transform the password to key.

The only KDF available is PBKDF2-SHA256 (via the pkcs5 hash type), everything else is non-standard AFAIK for this. PBKDF2 isn't really the choice I'd want, but I find it acceptable to use. This will incur more overhead for per-user keys however.

I've been going through sorting that out today, doveadm pw is incompatible (it only offers PBKDF2-SHA1 with non-configurable salt), so we'll need a different utility to generate the hash/key for encrypting the private key with. I ran into other gotchas along the way, but I believe I've resolved most of those.


Personally I think the available cipher suite is more problematic than the sha256 key.

What is problematic about it? Are you referring to TLS cipher suite, or ciphers used for encryption? AES-256-GCM is used for mail storage AFAIK, and AES-256-CTR for the private key.

I spent a fair amount of time carefully auditing TLS cipher suite support back in 2020. I would like to be more strict personally, but don't want to risk introducing issues (Apple Mail was one that lagged behind heavily until around 2018 or so IIRC, as did various mail servers back in 2016, and some users were still depending on devices from a decade ago that were EOL).


Note, to decrypt the private key you would have to generate random sha256 strings. So I'm not so sure it would be that easy to break.

256-bit entropy would be problematic yes, but that's not the case here.

If the attacker has access and is aware it's a DMS setup (along with read access to all the config), they should know that they can just setup hashcat to output SHA256 hashed passwords to try. There is plenty of low-hanging fruit to go for.

Users that know better, and have higher entropy passwords (more common these days with password managers) won't be at as much risk. However DMS cannot assume all our users that opt-in to the feature have good password practices :sweat_smile:

DavyLandman commented 2 years ago
  • encrypt the private key with the sha256 hash of the user password

This is my point...

Currently, the user without any encryption-at-rest involved will authenticate to retrieve mail via IMAP. The plaintext password is sent over TLS to Dovecot, and Dovecot will perform a lookup in the PassDB (we use the passwd-file driver)to know what Password Scheme to use (or if no hint was stored, fallback to the configured default that we've configured as SHA512-CRYPT), and process the users password with that Password Scheme to authenticate.

You probably knew all that, but this is where my point is. The computation required for the plaintext password through the Password Scheme is much more demanding on resources than a single SHA256 hash of the plaintext is.

The status of the active WIP PR, is the plaintext only went through a single SHA256 pass to derive the key to decrypt the private key used to decrypt the mail (encrypted via the public key).

If the concern was about protecting the content on the disk, should a breach occur, then the attacker will have an easy time against any weak entropy passwords no? All that is required is to send an email to the user, as the metadata is not encrypted and doveadm can be used to query the mail for that user by the subject or other metadata. Only the mail body is encrypted.

The private key could just as well be encrypted with a plaintext password in this case as the SHA256 hash isn't adding any real security. Once the mail is decrypted, the password is acquired, and that may be more valuable than the mail itself. My concern is with weakening the security of users actual accounts/passwords, by an admin enabling the mail encryption feature thinking the per-user key is more secure than the global encryption key. Under this context I don't think the per-user key is beneficial.

Okay, just to make sure we are on the same page. The sha256 hash is not stored anywhere. But if you wanted to bruteforce the password, you could indeed try to generate sha256's of the password, and see if that correctly decrypts the private key. So that indeed reduces the complexity of the password security. We could salt it with something to remove that possibility?

So, I'm only saying that hashcat and friends can't just compare a sha256 string, they'll have to try and decrypt, and see if the private key matches the public key (which is also not a "cheap" operation).

Switching away from sha256 would be nice, but that should be compatible with the interpolation syntax available in dovecot. As that is being used to transform the password to key.

The only KDF available is PBKDF2-SHA256 (via the pkcs5 hash type), everything else is non-standard AFAIK for this. PBKDF2 isn't really the choice I'd want, but I find it acceptable to use. This will incur more overhead for per-user keys however.

I've been going through sorting that out today, doveadm pw is incompatible (it only offers PBKDF2-SHA1 with non-configurable salt), so we'll need a different utility to generate the hash/key for encrypting the private key with. I ran into other gotchas along the way, but I believe I've resolved most of those.

Okay, that sounds good, a performance cost during login is fine. So if you can get that to work, I completely agree that would be better than the current approach. šŸ‘šŸ¼

Personally I think the available cipher suite is more problematic than the sha256 key.

What is problematic about it? Are you referring to TLS cipher suite, or ciphers used for encryption? AES-256-GCM is used for mail storage AFAIK, and AES-256-CTR for the private key.

I spent a fair amount of time carefully auditing TLS cipher suite support back in 2020. I would like to be more strict personally, but don't want to risk introducing issues (Apple Mail was one that lagged behind heavily until around 2018 or so IIRC, as did various mail servers back in 2016, and some users were still depending on devices from a decade ago that were EOL).

I used the wrong term. I meant the public&private key. (we discussed this last year: https://github.com/docker-mailserver/docker-mailserver/issues/2058#issuecomment-875399076 ) instead of secp521r1 I would have prefered Curve25519 based.

Note, to decrypt the private key you would have to generate random sha256 strings. So I'm not so sure it would be that easy to break.

256-bit entropy would be problematic yes, but that's not the case here.

If the attacker has access and is aware it's a DMS setup (along with read access to all the config), they should know that they can just setup hashcat to output SHA256 hashed passwords to try. There is plenty of low-hanging fruit to go for.

True, it's a bit more complicated, but indeed, it might end up being a shorter path.

Users that know better, and have higher entropy passwords (more common these days with password managers) won't be at as much risk. However DMS cannot assume all our users that opt-in to the feature have good password practices šŸ˜…

polarathene commented 2 years ago

Okay, just to make sure we are on the same page. The sha256 hash is not stored anywhere.

Yes, we're on the same page there. Users password derives a secret to decrypt the private key AFAIK, which then can decrypt the mail body.

We could salt it with something to remove that possibility?

Salt provides security against pre-computing hashes to try in advance when it's not known, otherwise only as a deterrent against a rainbow table style approach.

We could maybe store a random salt in an extra field, but as you noted since there is no direct hash to compare to, I think we could get away with the users account (test@example.com), that should be fairly effective still? Only a concern when a specific account is being targeted in preparation of a breach?

So, I'm only saying that hashcat and friends can't just compare a sha256 string, they'll have to try and decrypt, and see if the private key matches the public key (which is also not a "cheap" operation).

Yeah that's true :+1:

On a test without the KDF involved and just passing the resulting hash mail body is decrypted in about 60ms on my VM, I'm not sure if that has access to HW accel (AES-NI?)

I used the wrong term. I meant the public&private key. (we discussed this last year: https://github.com/docker-mailserver/docker-mailserver/issues/2058#issuecomment-875399076 ) instead of secp521r1 I would have prefered Curve25519 based.

Ah right. So nothing we can improve on our end then :(

I too would prefer Curve25519.

DavyLandman commented 2 years ago

We could maybe store a random salt in an extra field, but as you noted since there is no direct hash to compare to, I think we could get away with the users account (test@example.com), that should be fairly effective still? Only a concern when a specific account is being targeted in preparation of a breach?

True, the users-email added to the mix avoid any pre-computed attacks.. And building a precomputed list for a specific account would just beat the benefit of building the precomputed list right?

On a test without the KDF involved and just passing the resulting hash mail body is decrypted in about 60ms on my VM, I'm not sure if that has access to HW accel (AES-NI?)

True, but if you are running hashcat, you don't want to have like a 10ms operation just to compare the result. As it has to be executed for every generated password. Also, decrypting the private key might be hardware accelerated, the mechanics to calculate the public key from the private key will still be a "slow" process. So I think validating the password is quite slow.

Hmm, wait, a precomputed attack makes no sense, since you don't have the hash to start with, you only have an oracle to test an hash if it's valid. And that oracle is slow. So I think even adding a salt wouldn't do that much security wise. (as in, hashcat will need to generate sha256 strings based on the dictionaries anyway, adding a salt is not that much extra work).

In a way, the private&public key can be seen as a KDF, as in, it slows down the validation.

polarathene commented 2 years ago

And building a precomputed list for a specific account would just beat the benefit of building the precomputed list right?

Note that with the "No salt" case, since our user account password hashes are using salts with SHA512-CRYPT, and that the private key is likewise unique, even with the same password and no salt for deriving the same password to decrypt multiple private keys (of users with the same plaintext password), the attacker cannot easily lookup what accounts share the same password. The work to derive the key could otherwise be considered as avoided though, so a salt is still useful AFAIK.

So I think validating the password is quite slow.

I admit this is not something I know well enough. At 50ms per attempt (20 per second), it seems a year of constant validations would only manage around 630 million exhausting approx 29.23 bits of entropy. I have heard statistically the average password is around 40 bits of entropy, so at that rate it wouldn't seem so bad, but I am also not sure how much faster that can be computed on more specialized hardware :grimacing:

I am familiar with breaking asymmetric encryption with RSA key exchange and I think ECC was similar but a little bit more harder. I might recall that wrong, it was about breaking a certificate key to forge authenticity or for non-perfect-forward-secrecy decrypt past TLS encrypted communications.

I remember how to compare the strength of RSA to equivalent symmetric encryption security strength, not that it should matter as it's fairly impractical :sweat_smile:

Hmm, wait, a precomputed attack makes no sense, since you don't have the hash to start with,

It's not about having a hash to compare to or lookup (in the sense of a rainbow table attack). You'd just be eagerly computing the work in advance of a breach which can be helpful if calculating the hash is compute intensive itself, which permits more time to the validation step once a breach is achieved.

Once breached you'd just provide your pre-computed hashes like this:

doveadm -o plugin/mail_crypt_private_password='3b179c43182f245c90cff794ce357a4353a9bf71f56c83774a0943f0a018d0eb' fetch -u 'user@example.test' subject pwned mailbox-guid '42c9bc252dadf06227050000522ea783' uid 4

I think even adding a salt wouldn't do that much security wise. In a way, the private&public key can be seen as a KDF, as in, it slows down the validation.

Adding a salt is a non-issue, at least for the user account (test@example.com), it looks like this:

%{pkcs5;rounds=5000,truncate=1792,salt=%u,format=hex:password}

Where %u is populated as test@example.com by Dovecot. pkcs5 here being PBKDF-SHA256. I'll look into timing measurements a bit once the PR is up.

DavyLandman commented 2 years ago

It's not about having a hash to compare to or lookup (in the sense of a rainbow table attack). You'd just be eagerly computing the work in advance of a breach which can be helpful if calculating the hash is compute intensive itself, which permits more time to the validation step once a breach is achieved.

Once breached you'd just provide your pre-computed hashes like this:

doveadm -o plugin/mail_crypt_private_password='3b179c43182f245c90cff794ce357a4353a9bf71f56c83774a0943f0a018d0eb' fetch -u 'user@example.test' subject pwned mailbox-guid '42c9bc252dadf06227050000522ea783' uid 4

So, I think we agree with the solution direction, but to explain my reasoning: hashcat operation => N passwords take time: N*a + N*b + N*c where

For a normal hash you would have c be very low, since it's just comparying a 32 bytes. But for this case, it would mean that the c constant gets very high. As every generated hash would have to be used as a key for decryption. Then the decypted bytes have to be taken as a private key, and transformed to the corresponding public key, and only then can that public key be compared to the public key "on record". In my estimate, having an whole bunch of prepared sha256's of passwords won't make that much of a difference, as c will be the bottleneck.

(note that a seed would primarily influence the overhead of the b constant).

%{pkcs5;rounds=5000,truncate=1792,salt=%u,format=hex:password}

Where %u is populated as test@example.com by Dovecot. pkcs5 here being PBKDF-SHA256. I'll look into timing measurements a bit once the PR is up.

I agree, I like that better, it's cleaner. Nobody ever got fired for adding a salt ;)

georglauterbach commented 1 year ago

Will this really be tackled by someone for v13.0.0? I really don't mind when nobody has the time / resources to do so; I just want to know so I can close this issue and the corresponding PR (for my (personal) motivation, please see https://github.com/docker-mailserver/docker-mailserver/issues/3289#issuecomment-1520172545).

I know that @polarathene as absolutely packed with work, and I really don't want to bother him more than absolutely necessary at the moment. If you lack the time (I guess you do), please just tell me @polarathene :)

DavyLandman commented 1 year ago

I think more recent discussion on the status happened on #2080 . Would it maybe be an idea to mark/label this as "parked"?

georglauterbach commented 1 year ago

I think more recent discussion on the status happened on #2080 . Would it maybe be an idea to mark/label this as "parked"?

Technically yes, and I know that some maintainer's have no issue with that. For me though, it is a problem (albeit maybe a personal thing). My main motivation is that changes should be applied without letting them rot. #2080 is a nice PR, but from my PoV, when everyone says "let's park this", no one will actually tackle it. When I as a maintainer look at the currently open tasks (and I do a lot because maintaining DMS is an effort for me as well), I am always not amused when reading through this again, seeing that nothing has changes. I try to get my PRs merged all the time to avoid having nice changes that never see production..

I hope you can see where I'm coming from :) While currently this issue is marked with stale-bot/ignore, I would like to remove that. If you promise me to pick this issue / the corresponding PR up in the next weeks, I will refrain from removing the label for now.

DavyLandman commented 1 year ago

Whatever works for you.

I would suggest to have some inviting kind message indicating it's closed due to looking for someone to pick this up not because the project doesn't see this as a valid thing to add.

georglauterbach commented 1 year ago

I would suggest to have some inviting kind message indicating it's closed due to looking for someone to pick this up not because the project doesn't see this as a valid thing to add.

Definitely! I also adjusted #3289 to show that these PRs are not closed because they have been stale for a long time. I will make sure this is clearly communicated.

georglauterbach commented 1 year ago

Closing this issue as well. See https://github.com/docker-mailserver/docker-mailserver/pull/2080#issuecomment-1520782388. If you want to pick up these changes, please do so :) We encourage everyone to further work on this, and apply these changes and updates to the most recent version of master.

polarathene commented 1 year ago

If you lack the time (I guess you do), please just tell me @polarathene :)

As I've said in recent months, I have had this feature effectively ready on my end, I just have to rebase and adapt it to the changes since I last worked on it over 6 months ago. The problem is my time for the project is limited and I've been trying to keep momentum going with review / collaboration of PRs and bugs that keep requiring my attention..

I don't want the time I put into the feature to be wasted, so I'll hold off from participating in reviews where my input is not critical. Current priorities for DMS are:

  1. mydestination PR (breaking change, needs to consider many potential regressions).
  2. Resume: IPv6 revised docs (I need to verify if non-ULA subnet can be correctly configured / reachable between two hosts).
  3. Resume: Dovecot Quota WIP PR (local commits, was near completion before going on hold).
  4. Restore: Encryption feature PR (effectively done, needs fair amount of rework due to large refactoring / changes since in scripts and tests that likely prevent rebasing).

I am always not amused when reading through this again, seeing that nothing has changes. I try to get my PRs merged all the time to avoid having nice changes that never see production..

I generally try to accomplish the same, but as mentioned can only do so much with my time available. I wanted v12 released and focused on that.

I was expecting a rest period, but more work was being pushed for a quick v12.1 release, along with other changes that weren't necessary at the time to close other PRs. It's almost been 4 months for me where I've barely had time for personal projects / R&R, instead buried in volunteer commitments often with time pressure, one after another, overlapping. This has been burning me out, slowing down my productivity :\

The best I can do to address this, is reduce my commitments. I can resolve old issues / PRs if I'm not sinking time into new ones, as I am finding it difficult to keep up at this point, and taking time off fully for myself will just compound the workload pressure when returning.


I can adjust priorities of the list above if necessary.

The mydestination PR isn't due until v13, but I think would have been merged / approved without proper consideration of regressions / breakage, so I've been focused on that. I'd like to get all tests fully migrated, but I also don't want to see IPv6 docs left in the state they are, nor the encryption feature I worked on lost / abandoned.

At my current rate, pragmatically those tasks without any other interruptions can be resolved within 2-3 weeks. Realistically I have pressure to get paid work, that is expected to increase in May, so I will attempt to juggle these:

georglauterbach commented 1 year ago

The list of your current priorities looks absolutely fine to me, no need to change it! Part of the reason I'm also closing this is to address your time issue, because I know you've been loaded. You're often taking over such PRs - and this is the reason they come out so nicely - but of course, you will then need to dedicate time for it. I think this is the community's responsibility though :)

Please take your time with your list for DMS :) Right now, there is no more release pressure since the last PR for v12.1.0 will be reviewed by @casperklein, you needn't look into that @polarathene. Thereafter, we can take our time for v13.0.0. This also means the commitment for DMS should definitely be lower from now on, and I will make sure it stays this way :D

DavyLandman commented 1 year ago

hi @polarathene if I can help with reviewing your branch on the encryption, send me a ping.