From 0764f4fdf16f735f254d13653d75c494fa0f4d73 Mon Sep 17 00:00:00 2001 From: calle <93376500+callebtc@users.noreply.github.com> Date: Mon, 14 Feb 2022 17:54:05 +0100 Subject: [PATCH] Support for encrypted macaroons (#521) * encrypted macaroons * fix GRPC env entry * example config entry * add pycryptodomex to requirements * documentation * Added pycryptodomex to pip file Co-authored-by: Ben Arc --- .env.example | 4 + Pipfile | 1 + Pipfile.lock | 164 ++++++++++++++++------------ docs/guide/wallets.md | 12 +- lnbits/wallets/lndgrpc.py | 20 ++-- lnbits/wallets/lndrest.py | 10 +- lnbits/wallets/macaroon/__init__.py | 1 + lnbits/wallets/macaroon/macaroon.py | 103 +++++++++++++++++ requirements.txt | 3 +- 9 files changed, 235 insertions(+), 83 deletions(-) create mode 100644 lnbits/wallets/macaroon/__init__.py create mode 100644 lnbits/wallets/macaroon/macaroon.py diff --git a/.env.example b/.env.example index 060748a9..85773716 100644 --- a/.env.example +++ b/.env.example @@ -55,11 +55,15 @@ LND_GRPC_ENDPOINT=127.0.0.1 LND_GRPC_PORT=11009 LND_GRPC_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert" LND_GRPC_MACAROON="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/admin.macaroon or HEXSTRING" +# To use an AES-encrypted macaroon, set +# LND_GRPC_MACAROON_ENCRYPTED="eNcRyPtEdMaCaRoOn" # LndRestWallet LND_REST_ENDPOINT=https://127.0.0.1:8080/ LND_REST_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert" LND_REST_MACAROON="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/admin.macaroon or HEXSTRING" +# To use an AES-encrypted macaroon, set +# LND_REST_MACAROON_ENCRYPTED="eNcRyPtEdMaCaRoOn" # LNPayWallet LNPAY_API_ENDPOINT=https://api.lnpay.co/v1/ diff --git a/Pipfile b/Pipfile index 27aad746..6e738367 100644 --- a/Pipfile +++ b/Pipfile @@ -30,6 +30,7 @@ sse-starlette = "*" jinja2 = "3.0.1" pyngrok = "*" secp256k1 = "*" +pycryptodomex = "*" [dev-packages] black = "==20.8b1" diff --git a/Pipfile.lock b/Pipfile.lock index 220eed83..e77de500 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "bd78144379115a1566549f5f2d7dba7a96539fb5893b3999e61bc13c6847827e" + "sha256": "3e19364434fd2db3748162ccc1f3b6bddcf7a382473069d15cee6eda5e07eef1" }, "pipfile-spec": 6, "requires": { @@ -146,11 +146,11 @@ }, "charset-normalizer": { "hashes": [ - "sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45", - "sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c" + "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597", + "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df" ], "markers": "python_version >= '3.5'", - "version": "==2.0.11" + "version": "==2.0.12" }, "click": { "hashes": [ @@ -201,11 +201,11 @@ }, "httpcore": { "hashes": [ - "sha256:2621ee769d0236574df51b305c5f4c69ca8f0c7b215221ad247b1ee42a9a9de1", - "sha256:435ab519628a6e2393f67812dea3ca5c6ad23b457412cd119295d9f906d96a2b" + "sha256:47d772f754359e56dd9d892d9593b6f9870a37aeb8ba51e9a88b09b3d68cfade", + "sha256:7503ec1c0f559066e7e39bc4003fd2ce023d01cf51793e3c173b864eb456ead1" ], "markers": "python_version >= '3.6'", - "version": "==0.14.5" + "version": "==0.14.7" }, "httptools": { "hashes": [ @@ -427,6 +427,39 @@ ], "version": "==2.21" }, + "pycryptodomex": { + "hashes": [ + "sha256:1ca8e1b4c62038bb2da55451385246f51f412c5f5eabd64812c01766a5989b4a", + "sha256:298c00ea41a81a491d5b244d295d18369e5aac4b61b77b2de5b249ca61cd6659", + "sha256:2aa887683eee493e015545bd69d3d21ac8d5ad582674ec98f4af84511e353e45", + "sha256:2ce76ed0081fd6ac8c74edc75b9d14eca2064173af79843c24fa62573263c1f2", + "sha256:3da13c2535b7aea94cc2a6d1b1b37746814c74b6e80790daddd55ca5c120a489", + "sha256:406ec8cfe0c098fadb18d597dc2ee6de4428d640c0ccafa453f3d9b2e58d29e2", + "sha256:4d0db8df9ffae36f416897ad184608d9d7a8c2b46c4612c6bc759b26c073f750", + "sha256:530756d2faa40af4c1f74123e1d889bd07feae45bac2fd32f259a35f7aa74151", + "sha256:77931df40bb5ce5e13f4de2bfc982b2ddc0198971fbd947776c8bb5050896eb2", + "sha256:797a36bd1f69df9e2798e33edb4bd04e5a30478efc08f9428c087f17f65a7045", + "sha256:8085bd0ad2034352eee4d4f3e2da985c2749cb7344b939f4d95ead38c2520859", + "sha256:8536bc08d130cae6dcba1ea689f2913dfd332d06113904d171f2f56da6228e89", + "sha256:a4d412eba5679ede84b41dbe48b1bed8f33131ab9db06c238a235334733acc5e", + "sha256:aebecde2adc4a6847094d3bd6a8a9538ef3438a5ea84ac1983fcb167db614461", + "sha256:b276cc4deb4a80f9dfd47a41ebb464b1fe91efd8b1b8620cf5ccf8b824b850d6", + "sha256:b5a185ae79f899b01ca49f365bdf15a45d78d9856f09b0de1a41b92afce1a07f", + "sha256:c4d8977ccda886d88dc3ca789de2f1adc714df912ff3934b3d0a3f3d777deafb", + "sha256:c5dd3ffa663c982d7f1be9eb494a8924f6d40e2e2f7d1d27384cfab1b2ac0662", + "sha256:ca88f2f7020002638276439a01ffbb0355634907d1aa5ca91f3dc0c2e44e8f3b", + "sha256:d2cce1c82a7845d7e2e8a0956c6b7ed3f1661c9acf18eb120fc71e098ab5c6fe", + "sha256:d709572d64825d8d59ea112e11cc7faf6007f294e9951324b7574af4251e4de8", + "sha256:da8db8374295fb532b4b0c467e66800ef17d100e4d5faa2bbbd6df35502da125", + "sha256:e36c7e3b5382cd5669cf199c4a04a0279a43b2a3bdd77627e9b89778ac9ec08c", + "sha256:e95a4a6c54d27a84a4624d2af8bb9ee178111604653194ca6880c98dcad92f48", + "sha256:ee835def05622e0c8b1435a906491760a43d0c462f065ec9143ec4b8d79f8bff", + "sha256:f75009715dcf4a3d680c2338ab19dac5498f8121173a929872950f4fb3a48fbf", + "sha256:f8524b8bc89470cec7ac51734907818d3620fb1637f8f8b542d650ebec42a126" + ], + "index": "pypi", + "version": "==3.14.1" + }, "pydantic": { "hashes": [ "sha256:085ca1de245782e9b46cefcf99deecc67d418737a1fd3f6a4f511344b613a5b3", @@ -684,22 +717,22 @@ }, "typing-extensions": { "hashes": [ - "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e", - "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b" + "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42", + "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2" ], "index": "pypi", - "version": "==4.0.1" + "version": "==4.1.1" }, "uvicorn": { "extras": [ "standard" ], "hashes": [ - "sha256:8b16d9ecb76500f7804184f182835fe8a2b54716d3b0b6bb2da0b2b192f62c73", - "sha256:dffbacb8cc25d924d68d231d2c478c4fe6727c36537d8de21e5de591b37afc41" + "sha256:25850bbc86195a71a6477b3e4b3b7b4c861fb687fb96912972ce5324472b1011", + "sha256:e85872d84fb651cccc4c5d2a71cf7ead055b8fb4d8f1e78e36092282c0cf2aec" ], "index": "pypi", - "version": "==0.17.1" + "version": "==0.17.4" }, "uvloop": { "hashes": [ @@ -819,53 +852,50 @@ "toml" ], "hashes": [ - "sha256:012157499ec4f135fc36cd2177e3d1a1840af9b236cbe80e9a5ccfc83d912a69", - "sha256:0a34d313105cdd0d3644c56df2d743fe467270d6ab93b5d4a347eb9fec8924d6", - "sha256:11e61c5548ecf74ea1f8b059730b049871f0e32b74f88bd0d670c20c819ad749", - "sha256:152cc2624381df4e4e604e21bd8e95eb8059535f7b768c1fb8b8ae0b26f47ab0", - "sha256:1b4285fde5286b946835a1a53bba3ad41ef74285ba9e8013e14b5ea93deaeafc", - "sha256:27a94db5dc098c25048b0aca155f5fac674f2cf1b1736c5272ba28ead2fc267e", - "sha256:27ac7cb84538e278e07569ceaaa6f807a029dc194b1c819a9820b9bb5dbf63ab", - "sha256:2a491e159294d756e7fc8462f98175e2d2225e4dbe062cca7d3e0d5a75ba6260", - "sha256:2bc85664b06ba42d14bb74d6ddf19d8bfc520cb660561d2d9ce5786ae72f71b5", - "sha256:32168001f33025fd756884d56d01adebb34e6c8c0b3395ca8584cdcee9c7c9d2", - "sha256:3c4ce3b647bd1792d4394f5690d9df6dc035b00bcdbc5595099c01282a59ae01", - "sha256:433b99f7b0613bdcdc0b00cc3d39ed6d756797e3b078d2c43f8a38288520aec6", - "sha256:4578728c36de2801c1deb1c6b760d31883e62e33f33c7ba8f982e609dc95167d", - "sha256:509c68c3e2015022aeda03b003dd68fa19987cdcf64e9d4edc98db41cfc45d30", - "sha256:51372e24b1f7143ee2df6b45cff6a721f3abe93b1e506196f3ffa4155c2497f7", - "sha256:5d008e0f67ac800b0ca04d7914b8501312c8c6c00ad8c7ba17754609fae1231a", - "sha256:649df3641eb351cdfd0d5533c92fc9df507b6b2bf48a7ef8c71ab63cbc7b5c3c", - "sha256:6e78b1e25e5c5695dea012be473e442f7094d066925604be20b30713dbd47f89", - "sha256:72d9d186508325a456475dd05b1756f9a204c7086b07fffb227ef8cee03b1dc2", - "sha256:7d82c610a2e10372e128023c5baf9ce3d270f3029fe7274ff5bc2897c68f1318", - "sha256:7ee317486593193e066fc5e98ac0ce712178c21529a85c07b7cb978171f25d53", - "sha256:7eed8459a2b81848cafb3280b39d7d49950d5f98e403677941c752e7e7ee47cb", - "sha256:823f9325283dc9565ba0aa2d240471a93ca8999861779b2b6c7aded45b58ee0f", - "sha256:85c5fc9029043cf8b07f73fbb0a7ab6d3b717510c3b5642b77058ea55d7cacde", - "sha256:86c91c511853dfda81c2cf2360502cb72783f4b7cebabef27869f00cbe1db07d", - "sha256:8e0c3525b1a182c8ffc9bca7e56b521e0c2b8b3e82f033c8e16d6d721f1b54d6", - "sha256:987a84ff98a309994ca77ed3cc4b92424f824278e48e4bf7d1bb79a63cfe2099", - "sha256:9ed3244b415725f08ca3bdf02ed681089fd95e9465099a21c8e2d9c5d6ca2606", - "sha256:a189036c50dcd56100746139a459f0d27540fef95b09aba03e786540b8feaa5f", - "sha256:a4748349734110fd32d46ff8897b561e6300d8989a494ad5a0a2e4f0ca974fc7", - "sha256:a5d79c9af3f410a2b5acad91258b4ae179ee9c83897eb9de69151b179b0227f5", - "sha256:a7596aa2f2b8fa5604129cfc9a27ad9beec0a96f18078cb424d029fdd707468d", - "sha256:ab4fc4b866b279740e0d917402f0e9a08683e002f43fa408e9655818ed392196", - "sha256:bde4aeabc0d1b2e52c4036c54440b1ad05beeca8113f47aceb4998bb7471e2c2", - "sha256:c72bb4679283c6737f452eeb9b2a0e570acaef2197ad255fb20162adc80bea76", - "sha256:c8582e9280f8d0f38114fe95a92ae8d0790b56b099d728cc4f8a2e14b1c4a18c", - "sha256:ca29c352389ea27a24c79acd117abdd8a865c6eb01576b6f0990cd9a4e9c9f48", - "sha256:ce443a3e6df90d692c38762f108fc4c88314bf477689f04de76b3f252e7a351c", - "sha256:d1675db48490e5fa0b300f6329ecb8a9a37c29b9ab64fa9c964d34111788ca2d", - "sha256:da1a428bdbe71f9a8c270c7baab29e9552ac9d0e0cba5e7e9a4c9ee6465d258d", - "sha256:e4ff163602c5c77e7bb4ea81ba5d3b793b4419f8acd296aae149370902cf4e92", - "sha256:e67ccd53da5958ea1ec833a160b96357f90859c220a00150de011b787c27b98d", - "sha256:e8071e7d9ba9f457fc674afc3de054450be2c9b195c470147fbbc082468d8ff7", - "sha256:fff16a30fdf57b214778eff86391301c4509e327a65b877862f7c929f10a4253" + "sha256:1245ab82e8554fa88c4b2ab1e098ae051faac5af829efdcf2ce6b34dccd5567c", + "sha256:1bc6d709939ff262fd1432f03f080c5042dc6508b6e0d3d20e61dd045456a1a0", + "sha256:25e73d4c81efa8ea3785274a2f7f3bfbbeccb6fcba2a0bdd3be9223371c37554", + "sha256:276b13cc085474e482566c477c25ed66a097b44c6e77132f3304ac0b039f83eb", + "sha256:2aed4761809640f02e44e16b8b32c1a5dee5e80ea30a0ff0912158bde9c501f2", + "sha256:2dd70a167843b4b4b2630c0c56f1b586fe965b4f8ac5da05b6690344fd065c6b", + "sha256:352c68e233409c31048a3725c446a9e48bbff36e39db92774d4f2380d630d8f8", + "sha256:3f2b05757c92ad96b33dbf8e8ec8d4ccb9af6ae3c9e9bd141c7cc44d20c6bcba", + "sha256:448d7bde7ceb6c69e08474c2ddbc5b4cd13c9e4aa4a717467f716b5fc938a734", + "sha256:463e52616ea687fd323888e86bf25e864a3cc6335a043fad6bbb037dbf49bbe2", + "sha256:482fb42eea6164894ff82abbcf33d526362de5d1a7ed25af7ecbdddd28fc124f", + "sha256:56c4a409381ddd7bbff134e9756077860d4e8a583d310a6f38a2315b9ce301d0", + "sha256:56d296cbc8254a7dffdd7bcc2eb70be5a233aae7c01856d2d936f5ac4e8ac1f1", + "sha256:5e15d424b8153756b7c903bde6d4610be0c3daca3986173c18dd5c1a1625e4cd", + "sha256:618eeba986cea7f621d8607ee378ecc8c2504b98b3fdc4952b30fe3578304687", + "sha256:61d47a897c1e91f33f177c21de897267b38fbb45f2cd8e22a710bcef1df09ac1", + "sha256:621f6ea7260ea2ffdaec64fe5cb521669984f567b66f62f81445221d4754df4c", + "sha256:6a5cdc3adb4f8bb8d8f5e64c2e9e282bc12980ef055ec6da59db562ee9bdfefa", + "sha256:6c3f6158b02ac403868eea390930ae64e9a9a2a5bbfafefbb920d29258d9f2f8", + "sha256:704f89b87c4f4737da2860695a18c852b78ec7279b24eedacab10b29067d3a38", + "sha256:72128176fea72012063200b7b395ed8a57849282b207321124d7ff14e26988e8", + "sha256:78fbb2be068a13a5d99dce9e1e7d168db880870f7bc73f876152130575bd6167", + "sha256:7bff3a98f63b47464480de1b5bdd80c8fade0ba2832c9381253c9b74c4153c27", + "sha256:84f2436d6742c01136dd940ee158bfc7cf5ced3da7e4c949662b8703b5cd8145", + "sha256:9976fb0a5709988778ac9bc44f3d50fccd989987876dfd7716dee28beed0a9fa", + "sha256:9ad0a117b8dc2061ce9461ea4c1b4799e55edceb236522c5b8f958ce9ed8fa9a", + "sha256:9e3dd806f34de38d4c01416344e98eab2437ac450b3ae39c62a0ede2f8b5e4ed", + "sha256:9eb494070aa060ceba6e4bbf44c1bc5fa97bfb883a0d9b0c9049415f9e944793", + "sha256:9fde6b90889522c220dd56a670102ceef24955d994ff7af2cb786b4ba8fe11e4", + "sha256:9fff3ff052922cb99f9e52f63f985d4f7a54f6b94287463bc66b7cdf3eb41217", + "sha256:a06c358f4aed05fa1099c39decc8022261bb07dfadc127c08cfbd1391b09689e", + "sha256:a4f923b9ab265136e57cc14794a15b9dcea07a9c578609cd5dbbfff28a0d15e6", + "sha256:c5b81fb37db76ebea79aa963b76d96ff854e7662921ce742293463635a87a78d", + "sha256:d5ed164af5c9078596cfc40b078c3b337911190d3faeac830c3f1274f26b8320", + "sha256:d651fde74a4d3122e5562705824507e2f5b2d3d57557f1916c4b27635f8fbe3f", + "sha256:de73fca6fb403dd72d4da517cfc49fcf791f74eee697d3219f6be29adf5af6ce", + "sha256:e647a0be741edbb529a72644e999acb09f2ad60465f80757da183528941ff975", + "sha256:e92c7a5f7d62edff50f60a045dc9542bf939758c95b2fcd686175dd10ce0ed10", + "sha256:eeffd96882d8c06d31b65dddcf51db7c612547babc1c4c5db6a011abe9798525", + "sha256:f5a4551dfd09c3bd12fca8144d47fe7745275adf3229b7223c2f9e29a975ebda", + "sha256:fac0bcc5b7e8169bffa87f0dcc24435446d329cbc2b5486d155c2e0f3b493ae1" ], "markers": "python_version >= '3.7'", - "version": "==6.3" + "version": "==6.3.1" }, "iniconfig": { "hashes": [ @@ -948,11 +978,11 @@ }, "pytest": { "hashes": [ - "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89", - "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134" + "sha256:9ce3ff477af913ecf6321fe337b93a2c0dcf2a0a1439c43f5452112c1e4280db", + "sha256:e30905a0c131d3d94b89624a1cc5afec3e0ba2fbdb151867d8e0ebd49850f171" ], "index": "pypi", - "version": "==6.2.5" + "version": "==7.0.1" }, "pytest-cov": { "hashes": [ @@ -1051,10 +1081,10 @@ }, "tomli": { "hashes": [ - "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224", - "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1" + "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" ], - "version": "==2.0.0" + "version": "==2.0.1" }, "typed-ast": { "hashes": [ @@ -1088,11 +1118,11 @@ }, "typing-extensions": { "hashes": [ - "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e", - "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b" + "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42", + "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2" ], "index": "pypi", - "version": "==4.0.1" + "version": "==4.1.1" } } } diff --git a/docs/guide/wallets.md b/docs/guide/wallets.md index aaa141d2..4b19363a 100644 --- a/docs/guide/wallets.md +++ b/docs/guide/wallets.md @@ -36,16 +36,24 @@ Using this wallet requires the installation of the `grpcio` and `protobuf` Pytho - `LND_GRPC_ENDPOINT`: ip_address - `LND_GRPC_PORT`: port - `LND_GRPC_CERT`: /file/path/tls.cert -- `LND_GRPC_MACAROON`: /file/path/admin.macaroon +- `LND_GRPC_MACAROON`: /file/path/admin.macaroon or Bech64/Hex +You can also use an AES-encrypted macaroon (more info) instead by using + +- `LND_GRPC_MACAROON_ENCRYPTED`: eNcRyPtEdMaCaRoOn + +To encrypt your macaroon, run `./venv/bin/python lnbits/wallets/macaroon/macaroon.py`. ### LND (REST) - `LNBITS_BACKEND_WALLET_CLASS`: **LndRestWallet** - `LND_REST_ENDPOINT`: ip_address - `LND_REST_CERT`: /file/path/tls.cert -- `LND_GRPC_MACAROON`: /file/path/admin.macaroon +- `LND_REST_MACAROON`: /file/path/admin.macaroon or Bech64/Hex +or + +- `LND_REST_MACAROON_ENCRYPTED`: eNcRyPtEdMaCaRoOn ### LNbits diff --git a/lnbits/wallets/lndgrpc.py b/lnbits/wallets/lndgrpc.py index 3e030677..85c6dd09 100644 --- a/lnbits/wallets/lndgrpc.py +++ b/lnbits/wallets/lndgrpc.py @@ -11,6 +11,7 @@ import base64 import hashlib from os import environ, error, getenv from typing import Optional, Dict, AsyncGenerator +from .macaroon import load_macaroon, AESCipher if imports_ok: import lnbits.wallets.lnd_grpc_files.lightning_pb2 as ln @@ -58,12 +59,6 @@ def get_ssl_context(cert_path: str): return context -def load_macaroon(macaroon_path: str): - with open(macaroon_path, "rb") as f: - macaroon_bytes = f.read() - return macaroon_bytes.hex() - - def parse_checking_id(checking_id: str) -> bytes: return base64.b64decode(checking_id.replace("_", "/")) @@ -90,18 +85,19 @@ class LndWallet(Wallet): self.port = int(getenv("LND_GRPC_PORT")) self.cert_path = getenv("LND_GRPC_CERT") or getenv("LND_CERT") - macaroon_path = ( + macaroon = ( getenv("LND_GRPC_MACAROON") or getenv("LND_GRPC_ADMIN_MACAROON") or getenv("LND_ADMIN_MACAROON") or getenv("LND_GRPC_INVOICE_MACAROON") or getenv("LND_INVOICE_MACAROON") ) - - if macaroon_path.split(".")[-1] == "macaroon": - self.macaroon = load_macaroon(macaroon_path) - else: - self.macaroon = macaroon_path + + + encrypted_macaroon = getenv("LND_GRPC_MACAROON_ENCRYPTED") + if encrypted_macaroon: + macaroon = AESCipher(description="macaroon decryption").decrypt(encrypted_macaroon) + self.macaroon = load_macaroon(macaroon) cert = open(self.cert_path, "rb").read() creds = grpc.ssl_channel_credentials(cert) diff --git a/lnbits/wallets/lndrest.py b/lnbits/wallets/lndrest.py index 251bf52f..1c2a86a0 100644 --- a/lnbits/wallets/lndrest.py +++ b/lnbits/wallets/lndrest.py @@ -1,4 +1,5 @@ import asyncio +from pydoc import describe import httpx import json import base64 @@ -6,6 +7,7 @@ from os import getenv from typing import Optional, Dict, AsyncGenerator from lnbits import bolt11 as lnbits_bolt11 +from .macaroon import load_macaroon, AESCipher from .base import ( StatusResponse, @@ -34,7 +36,13 @@ class LndRestWallet(Wallet): or getenv("LND_INVOICE_MACAROON") or getenv("LND_REST_INVOICE_MACAROON") ) - self.auth = {"Grpc-Metadata-macaroon": macaroon} + + encrypted_macaroon = getenv("LND_REST_MACAROON_ENCRYPTED") + if encrypted_macaroon: + macaroon = AESCipher(description="macaroon decryption").decrypt(encrypted_macaroon) + self.macaroon = load_macaroon(macaroon) + + self.auth = {"Grpc-Metadata-macaroon": self.macaroon} self.cert = getenv("LND_REST_CERT") async def status(self) -> StatusResponse: diff --git a/lnbits/wallets/macaroon/__init__.py b/lnbits/wallets/macaroon/__init__.py new file mode 100644 index 00000000..b7cadcfe --- /dev/null +++ b/lnbits/wallets/macaroon/__init__.py @@ -0,0 +1 @@ +from .macaroon import load_macaroon, AESCipher \ No newline at end of file diff --git a/lnbits/wallets/macaroon/macaroon.py b/lnbits/wallets/macaroon/macaroon.py new file mode 100644 index 00000000..dd6ff636 --- /dev/null +++ b/lnbits/wallets/macaroon/macaroon.py @@ -0,0 +1,103 @@ +from Cryptodome import Random +from Cryptodome.Cipher import AES +import base64 +from hashlib import md5 +import getpass + +BLOCK_SIZE = 16 +import getpass + +def load_macaroon(macaroon: str) -> str: + """Returns hex version of a macaroon encoded in base64 or the file path. + + :param macaroon: Macaroon encoded in base64 or file path. + :type macaroon: str + :return: Hex version of macaroon. + :rtype: str + """ + + # if the macaroon is a file path, load it + if macaroon.split(".")[-1] == "macaroon": + with open(macaroon, "rb") as f: + macaroon_bytes = f.read() + return macaroon_bytes.hex() + else: + # convert the bas64 macaroon to hex + try: + macaroon = base64.b64decode(macaroon).hex() + except: + pass + return macaroon + +class AESCipher(object): + """This class is compatible with crypto-js/aes.js + + Encrypt and decrypt in Javascript using: + import AES from "crypto-js/aes.js"; + import Utf8 from "crypto-js/enc-utf8.js"; + AES.encrypt(decrypted, password).toString() + AES.decrypt(encrypted, password).toString(Utf8); + + """ + def __init__(self, key=None, description=""): + self.key = key + self.description = description + " " + + def pad(self, data): + length = BLOCK_SIZE - (len(data) % BLOCK_SIZE) + return data + (chr(length) * length).encode() + + + def unpad(self, data): + return data[: -(data[-1] if type(data[-1]) == int else ord(data[-1]))] + + @property + def passphrase(self): + passphrase = self.key if self.key is not None else None + if passphrase is None: + passphrase = getpass.getpass(f"Enter {self.description}password:") + return passphrase + + def bytes_to_key(self, data, salt, output=48): + # extended from https://gist.github.com/gsakkis/4546068 + assert len(salt) == 8, len(salt) + data += salt + key = md5(data).digest() + final_key = key + while len(final_key) < output: + key = md5(key + data).digest() + final_key += key + return final_key[:output] + + def decrypt(self, encrypted: str) -> str: + """Decrypts a string using AES-256-CBC. + """ + passphrase = self.passphrase + encrypted = base64.b64decode(encrypted) + assert encrypted[0:8] == b"Salted__" + salt = encrypted[8:16] + key_iv = self.bytes_to_key(passphrase.encode(), salt, 32 + 16) + key = key_iv[:32] + iv = key_iv[32:] + aes = AES.new(key, AES.MODE_CBC, iv) + try: + return self.unpad(aes.decrypt(encrypted[16:])).decode() + except UnicodeDecodeError: + raise ValueError("Wrong passphrase") + + def encrypt(self, message: bytes) -> str: + passphrase = self.passphrase + salt = Random.new().read(8) + key_iv = self.bytes_to_key(passphrase.encode(), salt, 32 + 16) + key = key_iv[:32] + iv = key_iv[32:] + aes = AES.new(key, AES.MODE_CBC, iv) + return base64.b64encode(b"Salted__" + salt + aes.encrypt(self.pad(message))).decode() + +# if this file is executed directly, ask for a macaroon and encrypt it +if __name__ == "__main__": + macaroon = input("Enter macaroon: ") + macaroon = load_macaroon(macaroon) + macaroon = AESCipher(description="encryption").encrypt(macaroon.encode()) + print("Encrypted macaroon:") + print(macaroon) diff --git a/requirements.txt b/requirements.txt index c23ebd31..b84e4ab6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,6 +25,7 @@ markupsafe==2.0.1 marshmallow==3.13.0 outcome==1.1.0 psycopg2-binary==2.9.1 +pycryptodomex==3.14.1 pydantic==1.8.2 pypng==0.0.21 pyqrcode==1.2.1 @@ -46,4 +47,4 @@ uvicorn==0.15.0 uvloop==0.16.0 watchgod==0.7 websockets==10.0 -zipp==3.5.0 +zipp==3.5.0 \ No newline at end of file