Open aakselrod opened 7 years ago
While collaborating on potential LSAT authentication schemes and use cases, @guggero think we may have come up with a protocol where discharge macaroons could be used in conjunction with LSATs for a kind of OAuth-like system.
To echo @aakselrod’s comment, this will certainly need some security analysis. That said, a few months ago, before the LSAT announcement, I built a proof of concept system that does something similar, using discharge macaroons issued by a server with access to a lightning node. The functionality is built into a Nodejs middleware that can be put in front of API routes where you require payment (the middleware is called boltwall, LSAT version in progress) and implemented in a live application called Prism Reader.
The goal for the protocol is to enable a platform, called a resource server (like Prism Reader) to check with another user’s lightning node, or an authorization server (the terminology for the roles is from RFC 6749 - The OAuth 2.0 Authorization Framework) that the client has permission to access some endpoint in the event that, for example, the endpoint provides data that “belongs” to the authorization server for which it wants to get paid before allowing access. This allows for systems that avoid the need for trusting a third party to collect payments and provide regular payouts since the resource server just needs to check with the authorizing server that they received payment (by validating a discharge macaroon). Using discharge macaroons also allows the authorizing server (i.e. the lightning node) to add their own caveats, further attenuating a macaroon. They could, for example, say:
I verify that this macaroon is associated with a payment I received and I grant the holder of it access as long as they make a request from [IP_ADDRESS] and within [X_NUM_DAYS].
One problem with the way this is currently implemented in boltwall is that it requires a large degree of coordination between the authorizing and resource servers in order to become aware of a shared “Caveat Key” used to sign and verify the discharge macaroon. In normal OAuth and OAuth-like systems, there is usually some kind of registration step and/or a handshake to establish these shared tokens. This might make sense when you have 1 platform using just a handful of OAuth services (e.g. Google, Facebook, and GitHub login). This becomes much more burdensome if, for example, every user on Medium.com or Twitter were required to support this type of interaction in order to receive payments.
The solution @guggero and I came up with is an Integrated Encryption Scheme that uses the known public key of the authorizing server’s node (Pn
) and an ‘ephemeral’ public/private key pair (Pe
and de
) to generate a secret signing key (s
), i.e. the caveat key, into the 3rd party caveat, allowing anyone that holds the root macaroon to read the encrypted secret, but only the authorizing server can decrypt the secret using their node’s private key (dn
). Once the secret has been decrypted, it can be used to sign the discharge macaroon.
Note that this is not currently possible with LND as it does not expose its private key in any way to perform the necessary KDF and ECC operations needed. @guggero has a PoC update to LND that will enable the protocol posted here: https://github.com/lightningnetwork/lnd/pull/3777
The IES protocol would look something like this:
de
(ephemeral private key) and Pe
(corresponding ephemeral public key where Pe = de * G
). Then using a Key Derivation Function (KDF, in our sample we use sha256
, but other symmetrical encryption schemes could also probably be used such as chacha20
or aes
), the ephemeral private key de
, and the authorizing server’s public key (Pn
), generate s
where s = KDF( de * Pn )
.
location = [auth_server_uri]
, identifier = Pe
, and caveat_key = s
.WWW-Authenticate
header they still must discharge the 3rd party caveat by sending a request to the auth_server_uri
with the root macaroon (this is like getting re-routed to Google to finish an OAuth login) Pe
whose private key de
was used to generate the secret s
KDF( de * Pn )
. The auth server does not know de
but does know Pe
since it’s in the caveat and knows its own private key dn
. So we need a way to get the same result as de * Pn
(i.e. our “shared public key”) with what we know:
de * Pn = de * (dn * G)
de * (dn * G) = (de * G) * dn
(de * G) * dn = Pe * dn
Pe * dn = de * Pn
Pe * dn
(ephemeral public key from the caveat multiplied by the node’s private key) and this can be used to generate the secret: s = KDF(Pe * dn) = KDF(Pn * de)
. The secret is then used to sign the discharge macaroon which, given the above assumptions, can only be forged by an attacker who knows either dn
or de
(Pe
and Pn
are publicly known but this doesn't undermine the assumptions)A code implementation of the above can be found and tested here https://github.com/lightningnetwork/lnd/pull/3777 (for lnd) and here (for client implementation in Node.js). You must build the branch of lnd with the walletrpc
flag and set your node’s credential information to the appropriate constants in the derive-key-example.js
file.
Below is a diagram illustrating in more detail the authorization flow between client, resource server, and authorization server including the use of LSATs. It is designed assuming a server running a boltwall middleware compatible with LSATs.
+1 IES protocol.
Currently,
lnd
doesn't use third-party caveats at all. This is a very powerful feature for using external state as auth information for a request; however, its use requires some detailed security analysis. Third party caveats allow external services to discharge them, thus allowing such features as: