mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-09-09 18:52:09 +02:00
cmd/lncli: add {encrypt,decrypt}debugpackage commands
This commit is contained in:
@@ -1,8 +1,20 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/andybalholm/brotli"
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
|
"github.com/lightningnetwork/lnd"
|
||||||
|
"github.com/lightningnetwork/lnd/lnencrypt"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
var getDebugInfoCommand = cli.Command{
|
var getDebugInfoCommand = cli.Command{
|
||||||
@@ -27,3 +39,297 @@ func getDebugInfo(ctx *cli.Context) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DebugPackage struct {
|
||||||
|
EphemeralPubKey string `json:"ephemeral_public_key"`
|
||||||
|
EncryptedPayload string `json:"encrypted_payload"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var encryptDebugPackageCommand = cli.Command{
|
||||||
|
Name: "encryptdebugpackage",
|
||||||
|
Category: "Debug",
|
||||||
|
Usage: "Collects a package of debug information and encrypts it.",
|
||||||
|
Description: `
|
||||||
|
When requesting support with lnd, it's often required to submit a lot of
|
||||||
|
debug information to the developer in order to track down a problem.
|
||||||
|
This command will collect all the relevant information and encrypt it
|
||||||
|
using the provided public key. The resulting file can then be sent to
|
||||||
|
the developer for further analysis.
|
||||||
|
Because the file is encrypted, it is safe to send it over insecure
|
||||||
|
channels or upload it to a GitHub issue.
|
||||||
|
|
||||||
|
Use 'lncli encryptdebugpackage 0xxxxxx... > package.txt' to write the
|
||||||
|
encrypted package to a file called package.txt.
|
||||||
|
`,
|
||||||
|
ArgsUsage: "pubkey [--output_file F]",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "pubkey",
|
||||||
|
Usage: "the public key to encrypt the information " +
|
||||||
|
"for (hex-encoded, e.g. 02aabb..), this " +
|
||||||
|
"should be provided to you by the issue " +
|
||||||
|
"tracker or developer you're requesting " +
|
||||||
|
"support from",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "output_file",
|
||||||
|
Usage: "(optional) the file to write the encrypted " +
|
||||||
|
"package to; if not specified, the debug " +
|
||||||
|
"package is printed to stdout",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: actionDecorator(encryptDebugPackage),
|
||||||
|
}
|
||||||
|
|
||||||
|
func encryptDebugPackage(ctx *cli.Context) error {
|
||||||
|
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
|
||||||
|
return cli.ShowCommandHelp(ctx, "encryptdebugpackage")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
args = ctx.Args()
|
||||||
|
pubKeyBytes []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
switch {
|
||||||
|
case ctx.IsSet("pubkey"):
|
||||||
|
pubKeyBytes, err = hex.DecodeString(ctx.String("pubkey"))
|
||||||
|
case args.Present():
|
||||||
|
pubKeyBytes, err = hex.DecodeString(args.First())
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to decode pubkey argument: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKey, err := btcec.ParsePubKey(pubKeyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to parse pubkey: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect the information we want to send from the daemon.
|
||||||
|
payload, err := collectDebugPackageInfo(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to collect debug package "+
|
||||||
|
"information: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We've collected the information we want to send, but before
|
||||||
|
// encrypting it, we want to compress it as much as possible to reduce
|
||||||
|
// the size of the final payload.
|
||||||
|
var (
|
||||||
|
compressBuf bytes.Buffer
|
||||||
|
options = brotli.WriterOptions{
|
||||||
|
Quality: brotli.BestCompression,
|
||||||
|
}
|
||||||
|
writer = brotli.NewWriterOptions(&compressBuf, options)
|
||||||
|
)
|
||||||
|
_, err = writer.Write(payload)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to compress payload: %w", err)
|
||||||
|
}
|
||||||
|
if err := writer.Close(); err != nil {
|
||||||
|
return fmt.Errorf("unable to compress payload: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we have the full payload that we want to encrypt, so we'll create
|
||||||
|
// an ephemeral keypair to encrypt the payload with.
|
||||||
|
localKey, err := btcec.NewPrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to generate local key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
enc, err := lnencrypt.ECDHEncrypter(localKey, pubKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to create encrypter: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var cipherBuf bytes.Buffer
|
||||||
|
err = enc.EncryptPayloadToWriter(compressBuf.Bytes(), &cipherBuf)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to encrypt payload: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := DebugPackage{
|
||||||
|
EphemeralPubKey: hex.EncodeToString(
|
||||||
|
localKey.PubKey().SerializeCompressed(),
|
||||||
|
),
|
||||||
|
EncryptedPayload: hex.EncodeToString(
|
||||||
|
cipherBuf.Bytes(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the user specified an output file, we'll write the encrypted
|
||||||
|
// payload to that file.
|
||||||
|
if ctx.IsSet("output_file") {
|
||||||
|
fileName := lnd.CleanAndExpandPath(ctx.String("output_file"))
|
||||||
|
jsonBytes, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to encode JSON: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.WriteFile(fileName, jsonBytes, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, we'll print out the final payload as a JSON if no output
|
||||||
|
// file was specified.
|
||||||
|
printJSON(response)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// collectDebugPackageInfo collects the information we want to send to the
|
||||||
|
// developer(s) from the daemon.
|
||||||
|
func collectDebugPackageInfo(ctx *cli.Context) ([]byte, error) {
|
||||||
|
ctxc := getContext()
|
||||||
|
client, cleanUp := getClient(ctx)
|
||||||
|
defer cleanUp()
|
||||||
|
|
||||||
|
info, err := client.GetInfo(ctxc, &lnrpc.GetInfoRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error getting info: %w", err)
|
||||||
|
}
|
||||||
|
debugInfo, err := client.GetDebugInfo(
|
||||||
|
ctxc, &lnrpc.GetDebugInfoRequest{},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error getting debug info: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var payloadBuf bytes.Buffer
|
||||||
|
addToBuf := func(msg proto.Message) error {
|
||||||
|
jsonBytes, err := lnrpc.ProtoJSONMarshalOpts.Marshal(msg)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error encoding response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
payloadBuf.Write(jsonBytes)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := addToBuf(info); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := addToBuf(debugInfo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return payloadBuf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var decryptDebugPackageCommand = cli.Command{
|
||||||
|
Name: "decryptdebugpackage",
|
||||||
|
Category: "Debug",
|
||||||
|
Usage: "Decrypts a package of debug information.",
|
||||||
|
Description: `
|
||||||
|
Decrypt a debug package that was created with the encryptdebugpackage
|
||||||
|
command. Decryption requires the private key that corresponds to the
|
||||||
|
public key the package was encrypted to.
|
||||||
|
The command expects the encrypted package JSON to be provided on stdin.
|
||||||
|
If decryption is successful, the information will be printed to stdout.
|
||||||
|
|
||||||
|
Use 'lncli decryptdebugpackage 0xxxxxx... < package.txt > decrypted.txt'
|
||||||
|
to read the encrypted package from a file called package.txt and to
|
||||||
|
write the decrypted content to a file called decrypted.txt.
|
||||||
|
`,
|
||||||
|
ArgsUsage: "privkey [--input_file F]",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "privkey",
|
||||||
|
Usage: "the hex encoded private key to decrypt the " +
|
||||||
|
"debug package",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "input_file",
|
||||||
|
Usage: "(optional) the file to read the encrypted " +
|
||||||
|
"package from; if not specified, the debug " +
|
||||||
|
"package is read from stdin",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: actionDecorator(decryptDebugPackage),
|
||||||
|
}
|
||||||
|
|
||||||
|
func decryptDebugPackage(ctx *cli.Context) error {
|
||||||
|
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
|
||||||
|
return cli.ShowCommandHelp(ctx, "decryptdebugpackage")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
args = ctx.Args()
|
||||||
|
privKeyBytes []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
switch {
|
||||||
|
case ctx.IsSet("pubkey"):
|
||||||
|
privKeyBytes, err = hex.DecodeString(ctx.String("pubkey"))
|
||||||
|
case args.Present():
|
||||||
|
privKeyBytes, err = hex.DecodeString(args.First())
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to decode privkey argument: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
privKey, _ := btcec.PrivKeyFromBytes(privKeyBytes)
|
||||||
|
|
||||||
|
// Read the file from stdin and decode the JSON into a DebugPackage.
|
||||||
|
var pkg DebugPackage
|
||||||
|
if ctx.IsSet("input_file") {
|
||||||
|
fileName := lnd.CleanAndExpandPath(ctx.String("input_file"))
|
||||||
|
jsonBytes, err := os.ReadFile(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to read file '%s': %w",
|
||||||
|
fileName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(jsonBytes, &pkg)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to decode JSON: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = json.NewDecoder(os.Stdin).Decode(&pkg)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to decode JSON: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the ephemeral public key and encrypted payload.
|
||||||
|
ephemeralPubKeyBytes, err := hex.DecodeString(pkg.EphemeralPubKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to decode ephemeral public key: %w",
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
encryptedPayloadBytes, err := hex.DecodeString(pkg.EncryptedPayload)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to decode encrypted payload: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the ephemeral public key and create an encrypter.
|
||||||
|
ephemeralPubKey, err := btcec.ParsePubKey(ephemeralPubKeyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to parse ephemeral public key: %w",
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
enc, err := lnencrypt.ECDHEncrypter(privKey, ephemeralPubKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to create encrypter: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt the payload.
|
||||||
|
decryptedPayload, err := enc.DecryptPayloadFromReader(
|
||||||
|
bytes.NewReader(encryptedPayloadBytes),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to decrypt payload: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decompress the payload.
|
||||||
|
reader := brotli.NewReader(bytes.NewBuffer(decryptedPayload))
|
||||||
|
decompressedPayload, err := io.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to decompress payload: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(string(decompressedPayload))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@@ -460,6 +460,8 @@ func main() {
|
|||||||
channelBalanceCommand,
|
channelBalanceCommand,
|
||||||
getInfoCommand,
|
getInfoCommand,
|
||||||
getDebugInfoCommand,
|
getDebugInfoCommand,
|
||||||
|
encryptDebugPackageCommand,
|
||||||
|
decryptDebugPackageCommand,
|
||||||
getRecoveryInfoCommand,
|
getRecoveryInfoCommand,
|
||||||
pendingChannelsCommand,
|
pendingChannelsCommand,
|
||||||
sendPaymentCommand,
|
sendPaymentCommand,
|
||||||
|
2
go.mod
2
go.mod
@@ -3,6 +3,7 @@ module github.com/lightningnetwork/lnd
|
|||||||
require (
|
require (
|
||||||
github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82
|
github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82
|
||||||
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344
|
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344
|
||||||
|
github.com/andybalholm/brotli v1.0.3
|
||||||
github.com/btcsuite/btcd v0.23.5-0.20230905170901-80f5a0ffdf36
|
github.com/btcsuite/btcd v0.23.5-0.20230905170901-80f5a0ffdf36
|
||||||
github.com/btcsuite/btcd/btcec/v2 v2.3.2
|
github.com/btcsuite/btcd/btcec/v2 v2.3.2
|
||||||
github.com/btcsuite/btcd/btcutil v1.1.4-0.20230904040416-d4f519f5dc05
|
github.com/btcsuite/btcd/btcutil v1.1.4-0.20230904040416-d4f519f5dc05
|
||||||
@@ -75,7 +76,6 @@ require (
|
|||||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
|
||||||
github.com/aead/siphash v1.0.1 // indirect
|
github.com/aead/siphash v1.0.1 // indirect
|
||||||
github.com/andybalholm/brotli v1.0.3 // indirect
|
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/btcsuite/btcwallet/wallet/txsizes v1.2.3 // indirect
|
github.com/btcsuite/btcwallet/wallet/txsizes v1.2.3 // indirect
|
||||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect
|
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect
|
||||||
|
Reference in New Issue
Block a user