Closed rdaum closed 6 months ago
I would think that for now, you should be able to replace the underlying crypt() builtin with some sort of hash, and have that builtin ignore any salt passed to it. If I understand the way the whole thing works, this should still work, without modifying any MOO code. The code crypts a password, gets a hash back, stores that, then when it needs to check it, it crypts the supplied password, also supplies the first two chars as salt (which a new crypt builtin would ignore) and gets a hash back, and if they match, we're good.
You're probably right it would be possible to make it work that way, but I'm still not convinced that having passwords in-core is the right story at all... and I'm pretty sure what we're describing here is not good security practice in this day and age.
And there's no reason in this day one couldn't support e.g. OAuth2, with just tying the 'player' object ID to external credentials, and rely on other, better-structured/audited, etc. security services to carry the weight & risk.
All this is true. However, if you did make the crypt() builtin hash instead of encrypt, you could have an unmodified LambdaCore, right out of the gate, that doesn't store passwords (not even crypted ones) in the database.
The code crypts a password, gets a hash back, stores that, then when it needs to check it, it crypts the supplied password, also supplies the first two chars as salt (which a new crypt builtin would ignore) and gets a hash back, and if they match, we're good.
The code actually supplies the entire crypted password as the salt (even though the documentation for crypt() says not to do that), and the crypt() function itself is responsible for reducing it to two characters. Which I guess means you could have crypt() implement whatever the current standard for password hashing is and there's no reason that wouldn't cut it.
Personally I don't understand the arguments against storing passwords in-database - my proposed scheme is basically identical to the way that any other login-requiring website works. I thought the idea of MOO was to do as little as possible in-driver and as much as possible in-core.
Hi. Looking deeply into this, I wonder why not using SSH instead? While most clients are very well able to connect via telnet, they cannot utilize all the neat features of SSH, yes, for that it would be necessary to build some sort of local proxy to translate from the outbound SSH-connection to an internal telnet connection, but this way all features of SSH could be used, including public key encryption, SSO etc. Using this, I would guess it is possible to use the same SSO-backend for both http and pseudo-telnet connections. Just some thoughts from a user perspective. Yes, it breaks compatibility with telnet, on the other side it solves almost all security issues. Telnet is a security risk by itself anyway.
Hi. Looking deeply into this, I wonder why not using SSH instead?
Sorry to not reply to this for a while. I have been busy with real, paid, work, and haven't had time to give to moor in a while.
But to update, what's actually happened here is that I've chosen to prioritize the web/websockets story over everything else. The expected primary mode of interaction with the moo hosted in moor is to be over HTTP and websockets (and, likely, in the future webtransport). The line-command oriented style of LambdaMOO is supported over websockets (which are kinda like 21st century "telnet", really), but verb invocations, property access, and eval
are also supported over (authenticated) HTTP GET/POST.
And because of that what I ended up implementing for authorization is the following:
in web-host
, HTTP authorization is established through two URLs (/ws/connect
and /ws/create
) which take HTTP basic auth (presumably only over an HTTPS connection). They then contact the moor
backend (daemon
) over ZMQ RPC, do standard MOO password authentication (salted hash but just for now) and then establish PASETO
auth tokens. (PASETO
is a little bit like jwt
but better. )
Then the actual sessions -- either websocket, or HTTP -- have to provide a valid PASETO token for each connection. For the websocket, this is passed as a query string parameter before the websocket upgrade. For HTTP POST/GET it is passed in HTTP headers. (Browsers unfortunately do not support custom HTTP headers for websocket connections, which boggles my mind).
This was all implemented back a couple months ago, along with a primitive web client that lets you connect, authenticate, does the command loop over the websocket, but also has a prototype (but incomplete) Smalltalk-style object browser and syntax highlight MOO verb code editor (using the same editor that drives VSCode).
But I have not had time to write it up and document it yet. I still consider the last bit prototype phase.
LambdaMOO cores'
connect
function uses a variant of the Unixcrypt
function, ala old Unix passwd files, and stores the passwords in-db.For compatibility with existing cores (and to make servicing existing
telnet
clients feasible), it's necessary to keep this process for now. But this isn't going to cut it as a security story.Some kind of SSO story & auth token is going to be needed. For the latter, one option is
JWT
, another isPASETO
, which sounds more promising.Then the websocket connection (and any HTTP sessions generally) will need to be authenticated with this token before client connections can proceed.
The long-term future for the
telnet
modality will need to be thought about in this context.Not a requirement for 1.0 (LambdaMOO compatible) release.