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:
using gateways to directory services such as LDAP or Active Directory to authenticate/authorize requests, limit them to certain users or groups, etc.
using custom-written auth back-ends to authenticate/authorize requests using custom criteria.
The text was updated successfully, but these errors were encountered:
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: #3777
The IES protocol would look something like this:
Resource server generates an LSAT-compatible root macaroon. In addition to the “normal” caveats, it will create a 3rd party caveat. To generate the caveat key used to sign and verify the discharge macaroon, the resource server creates 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 ).
The 3rd party caveat is created in such a way where: location = [auth_server_uri], identifier = Pe, and caveat_key = s.
The client is given the above root macaroon but after paying the invoice indicated in the 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)
The authorizing server parses the root macaroon to find the invoice_id and the 3rd party caveat associated with its uri. From the 3rd party caveat, it can get the Pe whose private key de was used to generate the secret s
In order to generate a discharge macaroon, the auth server must derive the secret so it can sign the discharge macaroon with it. To do this we can take advantage of the symmetry of ECC. The secret was generated with 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
Therefore without the ephemeral private key, we can still get the shared public key by calculating 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 #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.