Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discussion: proposed improvements to app passwords #310

Open
candlerb opened this issue Apr 7, 2023 · 2 comments
Open

Discussion: proposed improvements to app passwords #310

candlerb opened this issue Apr 7, 2023 · 2 comments

Comments

@candlerb
Copy link
Contributor

candlerb commented Apr 7, 2023

Preamble

Firstly, let me say glauth is very close to what I've been looking for. There are lots of services today that still don't support OIDC/OAUTH2, but LDAP is widely implemented, for both authentication and group membership. LDAP bind with a single master password is a pretty awful way to do authentication, but if you force OTP for interactive logins, and strong random app passwords for anything which is hard-coded into a client, then it becomes far better.

Glauth's OTP and app password implementation is simple, and as a general principle, simple = good as far as security is concerned! I have tested with Yubikey OTP and it's an absolute breeze to set up.

However, I would like to note a few fundamental problems as it stands.

Issue description

Let's say joeuser has three app passwords: one for laptop wifi, one for phone wifi, and one for mail client. These are in addition to his "master" password which is used in conjunction with a Yubikey OTP.

The problems today are:

  1. App passwords are not bound to individual apps; any app password can authenticate to any app. So if you can steal joeuser's wifi app password, you can use it to access his mail.
  2. App passwords can bypass OTP for any bind. Say there's a management web application that joeuser has access to, and authenticates with his primary password plus OTP (validated by glauth). If someone steals his wifi app password, that can be used to login to the management web application as well, without any OTP.
  3. There's no metadata to keep track of which app password is which. So if joeuser's phone gets stolen, he won't be able to revoke his phone's wifi password (unless he kept a separate record of all the passwords, or at least their hashes)

I'd like to propose a way to fix this whilst remaining accessible to "standard" LDAP protocol and clients.

Firstly, each app password can be split into a gecos-like format: <applabel>:<hash>:<comment> [^1]

  passappsha256 = [
    "wifi:c32255dbf6fd6b64883ec8801f793bccfa2a860f2b1ae1315cd95cdac1338efa:iPhone SE", # TestAppPw1
    "wifi:c9853d5f2599e90497e9f8cc671bd2022b0fb5d1bd7cfff92f079e8f8f02b8d3:Macbook Pro", # TestAppPw2
    "mail:4939efa7c87095dacb5e7e8b8cfb3a660fa1f5edcc9108f6d7ec20ea4d6b3a88:Thunderbird on Macbook", # TestAppPw3
  ]

The "applabel" is chosen by the system administrator to identify each application. The "comment" can be anything that the user chooses, to help remember where this password was deployed.

Now, we need to make sure that the app passwords can only be used when accessing that particular app. The only way we can select this is at LDAP Bind time.

[EDIT] (Although each app will have its own service account - and therefore can be identified when binding as itself - this doesn't apply when rebinding to validate a user's password; this may occur through a separate TCP connection. So we need some other way to identify which particular app a user's password is being validated for)

In interesting approach is via the username, by including a unique tag that identifies an app password. For example, for account "brian@example.com", I could present username "brian+wifi1@example.com" or "brian+wifi2@example.com" when using app password "wifi1" or "wifi2" respectively. This is a little bit more work for the user, but has a notable advantage: it allows strong SASL and EAP mechanisms like SCRAM or EAP-EKE, which require the correct cleartext password to be selected before authentication starts[^2].

What if we want to stay with what glauth does today, which is a single username, and validate a plaintext password against a list of hashes? We still need to know which app is being used.

A simple approach would be to have multiple listeners, accepting connections on multiple TCP ports (and/or IPs), where each port is associated with a specific app. This approach is easy to understand and implement - it could be a bit annoying if there are intervening firewalls to manage, but you could always define a consecutive range of ports for glauth.

Instead of ports, the TLS SNI hostname could be used. (e.g. "wifi.ldap.example.net" and "mail.ldap.example.net" both point to the same server). That would only work for TLS connections - but since app passwords are static and must be protected by TLS, maybe that's not an unreasonable requirement. The server would either need a wildcard cert, or separate certs for each app/servername. (Unfortunately, glauth doesn't support STARTTLS today, because of a weakness in the underlying LDAP library, but this would be an additional incentive to fix it. Otherwise, clients would be required to use LDAPS on port 636)

A more difficult approach would be to have a different DN for each use case. For example:

cn=joeuser,ou=users,dc=glauth,dc=com           # default: can bind with main password + OTP only
cn=joeuser,ou=wifi,ou=apps,dc=glauth,dc=com    # can bind with any "wifi" app password (or main+OTP)
cn=joeuser,ou=mail,ou=apps,dc=glauth,dc=com    # can bind with any "mail" app password (or main+OTP)

(It doesn't have to be exactly this format: just some way to uniquely identify DNs so there are separate subtrees for each app)

Then, we need to ensure that there are search operations that return the correct DNs. For example, the mail application should search under subtree ou=mail,ou=apps,dc=glauth,dc=com with filter (cn=joeuser), and will get cn=joeuser,ou=mail,ou=apps,dc=glauth,dc=com as the DN of the result. Searches under the top level dc=glauth,dc=com should not descend into ou=apps by default.

This is now well into the realm of "unusual" LDAP behaviour, but there's nothing wrong with this, and indeed you'd have to do something like this if you were trying to do app-specific passwords with a standard LDAP server like 389DS. You'd create a separate subtree of user entries for each app, so that each app+user can have its own bind password. With glauth, you could avoid having to duplicate the user data for each app, as it can synthesise the "ou=apps" subtrees itself.

The only potential problem I can see is around group memberships. It works fine if the application reads the posixAccount record and looks for "memberOf" attributes. But if the application first reads a group and looks for "uniqueMember" attributes (which are DNs) then those DNs will be the primary DNs, not the app-specific DNs. I don't see this as a major problem, as I doubt many apps will work this way. "memberUid" works fine, as it's not a DN.

There is one other idea which is to use bearer tokens with the XOAUTH2 or OAUTHBEARER SASL mechanisms. The first part of the token could be used as a key to select which app password is used, and the remainder can hashed in the database. However this doesn't work for EAP mechanisms which need a secret to be selected up-front; and the applicability of these SASL mechanisms is limited (and also really requires a web interface for issuing the tokens).

If you think there's value in any of these approaches then I can probably find time to work on it, but I think it would be important to fix the existing inconsistencies around DNs first (#181)


Finally: I don't think app passwords are currently supported with a SQL backend. The documented schema doesn't show passappsha256 or passappbcrypt, and the SELECT in the source doesn't appear to reference them.

Since they are inherently multi-value, maybe they should sit in a table of their own? Otherwise, it could be a JSON list of strings, similar to how custattr is implemented.

I think this is important, as I'd want users to be able to manage their own app passwords, which means being able to update them dynamically through a web application.


[^1] If doing this, another option would be to make the hash type an extra field too. This avoids the need for separate passappsha256 and passappbcrypt fields, and is more extensible. For example, some very strong authentication mechanisms like EAP-PWD and EAP-EKE require a 2-way encrypted plaintext password.

[^2] EAP-EKE in particular is very strong against rogue access points; it gives full mutual authentication between the client and the AP, without requiring the client to validate any certificate. Unfortunately, only Linux and Android support it.

@Fusion
Copy link
Collaborator

Fusion commented Sep 9, 2023

First, I want to apologize for waiting so long to address your well thought-out writeup.
I found a job that's challenging enough to steal my free time away from FOSS!
So, I guess it's both a curse and a blessing.

Moving on from this unnecessary preface, yes I share your belief in strong security. I was actually thinking that my first task now that I have a bit of free time, was going to be researching how to implement ZPK passwords. But what you are writing about here is more on the "useful" and "getting stuff done" side of things, so it deserves to take priority.

Regarding the username format, yes it would be a bit more work for the user, but it would also be insignificant as it's an app-specific passwords, so by definition not something they have to remember to type every time.
Having spent way too much time living with many-port implementations lately, thanks to RTP, I am a bit less excited about that alternate approach.
Regarding the multiple DN approach, GLAuth is expected to maintain a balance between multiple backends, maximum simplicity, and correctness. The latter, in particular, requires that I do not mess around too much with the schema, as clients (especially machine ones) have all sorts of very rigid expectations. So, I do not think it would be desirable to multiply RDNs for the same entity. You mention groups membership as a potential challenge, and, well, take a look as past tickets 😆

Regarding compatibility with EAP compatibility, yes it is definitely something I want to preserve at all cost. Plus, we are currently quite far removed from supporting SASL.

Digging deeper, I would like to gain a better understanding of the real-life use case: the validity of this approach can only be confirmed if we know that we have a prevalence of scenarios where one password being stolen doesn't mean that all of that user's passwords are now compromised. Thanks to jokers like Laspass, this is becoming a real concern. It seems to me that the scenario being covered here is either:

  • communication happened over unencrypted channels and credentials were snifffed
  • worse, encryption at rest on the provider side was dreadful. But, oops, we are the provider in this scenario. So, isn't it a fair assumption that all their app passwords were thus stolen?

Please share your opinion on the above.

Also, yes, I need to ensure that we have parity for all features in SQL so I'll double check app password support.

@candlerb
Copy link
Contributor Author

candlerb commented Sep 10, 2023

Thank you for your thoughtful reply. I agree that multiple DNs per app is extremely ugly, and I only included it to cover every option I had thought of :-)

Regarding compatibility with EAP compatibility, yes it is definitely something I want to preserve at all cost. Plus, we are currently quite far removed from supporting SASL.

The easiest way to do either SASL or EAP (e.g. for E-mail or wifi authentication), would be for an LDAP search by the service account to return the user's cleartext password, and let the server perform the user authentication. This allows existing applications like cyrus-sasl and FreeRADIUS to work.

Proxying SASL end-to-end via an LDAP SASL bind would be funky. Doing EAP over this would be even more funky. Searching around: RFC7055 suggests it could be done, as EAP over GSS-API over SASL over LDAP (!)

Whilst it might be fun to implement LDAP SASL binds in glauth, I don't see them having much practical use, as I don't know of any clients which would attempt a proxy LDAP SASL bind for a user authentication.

the validity of this approach can only be confirmed if we know that we have a prevalence of scenarios where one password being stolen doesn't mean that all of that user's passwords are now compromised

I think there are a number of cases.

My number one use case is Wifi authentication with PEAP. The security of this depends critically on every client device being correctly configured to validate the certificate presented by the RADIUS server via the wireless AP. However, there are too many client devices where you can just click a button to skip validation entirely. There are other clients where you effectively are forced to use a private CA (e.g. they are not usable with Letsencrypt certs for reasons I won't go into here). And that means it's insecure unless the client has manually loaded the private CA cert and trusts it for wifi.

These things can be done right, but it's hard, and many users will simply bypass it, and you can't enforce secure client practices at the server side.

This means that wifi passwords are highly sniffable: all you need to do is put up a rogue AP with the same SSID, and you're very likely to capture at least some of them. This is similar to your scenario "communication happened over unencrypted channels and credentials were snifffed"

Given that wifi app passwords are quite likely to be stolen, I think it's critical that they should not be usable to access any other service, and especially not any other service which normally requires an OTP (like the user's master account). Your only concern then is these captured passwords being used to access wifi, and this can be monitored - e.g. looking for two devices accessing with the same password.

Number two use case is app-specific passwords stored in a mail client like Thunderbird. Thunderbird has an option where you can click to show all the stored passwords. Hence if you sneak up on someone's machine and the screen is not locked, you can quite probably get hold of their mail password. (An alternative to app specific passwords here is to do the OAUTH2 dance, but that's a whole different story, and really the OAUTH2 bearer token you end up with is pretty much an app specific password anyway). Again, you don't want to allow privilege escalation beyond being able to read mail.

Third use case is a lost or stolen device. Whether or not the creds can be extracted like Thunderbird, they may be usable in-place. Either way, you want to invalidate them, but not all other creds for device which haven't been compromised.

encryption at rest on the provider side was dreadful. But, oops, we are the provider in this scenario. So, isn't it a fair assumption that all their app passwords were thus stolen?

Correct, and I'm not worried about this case. Actually, glauth today only stores hashes of passwords, and if those are sufficient long randomly-generated passwords then it's not a problem.

If glauth starts storing cleartext passwords then clearly you need to secure your server properly, although this is the same as any other system that uses symmetric encryption for authentication, like a Kerberos KDC.

You could if you wish delegate this to another system, like Hashicorp Vault (which has a very good story around managing secret material). Ultimately though, the server will still possess some token which allows it to access Vault. If you don't want to hard-code this on disk, you could prompt for it on startup. It's a similar issue to protecting private keys for SSL on a webserver.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants