mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 09:53:54 +01:00
cmd/lncli: add {encrypt,decrypt}debugpackage commands
This commit is contained in:
parent
b6abede4a3
commit
d4f49cb04b
@ -1,8 +1,20 @@
|
||||
package main
|
||||
|
||||
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/urfave/cli"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
var getDebugInfoCommand = cli.Command{
|
||||
@ -27,3 +39,297 @@ func getDebugInfo(ctx *cli.Context) error {
|
||||
|
||||
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,
|
||||
getInfoCommand,
|
||||
getDebugInfoCommand,
|
||||
encryptDebugPackageCommand,
|
||||
decryptDebugPackageCommand,
|
||||
getRecoveryInfoCommand,
|
||||
pendingChannelsCommand,
|
||||
sendPaymentCommand,
|
||||
|
2
go.mod
2
go.mod
@ -3,6 +3,7 @@ module github.com/lightningnetwork/lnd
|
||||
require (
|
||||
github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82
|
||||
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/btcec/v2 v2.3.2
|
||||
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/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // 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/btcsuite/btcwallet/wallet/txsizes v1.2.3 // indirect
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect
|
||||
|
Loading…
Reference in New Issue
Block a user