Skip to content

Commit 30cefc3

Browse files
committed
rpcserver+firewall: obfuscate configuration
We obfuscate pubkeys, channel points and ids entered in configurations. The channel id lengths for different block heights can be checked with: ```python len(str(1 << 40 | 2923 << 16 | 30)) len(str(10_000_000 << 40 | 2923 << 16 | 30)) ```
1 parent 3112fdc commit 30cefc3

File tree

3 files changed

+219
-1
lines changed

3 files changed

+219
-1
lines changed

firewall/privacy_mapper.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ package firewall
33
import (
44
"context"
55
"crypto/rand"
6+
"encoding/hex"
7+
"encoding/json"
68
"errors"
79
"fmt"
810
"math/big"
11+
"strconv"
12+
"strings"
913
"time"
1014

1115
"github.com/lightninglabs/lightning-terminal/firewalldb"
@@ -30,6 +34,15 @@ const (
3034
// between which timeVariation can be set.
3135
minTimeVariation = time.Minute
3236
maxTimeVariation = time.Duration(24) * time.Hour
37+
38+
// min and maxChanIDLen are the lengths to consider an int to be a
39+
// channel id. 13 corresponds to block height 1 and 20 to block height
40+
// 10_000_000.
41+
minChanIDLen = 13
42+
maxChanIDLen = 20
43+
44+
// pubKeyLen is the length of a node pubkey.
45+
pubKeyLen = 66
3346
)
3447

3548
var (
@@ -833,3 +846,103 @@ func CryptoRandIntn(n int) (int, error) {
833846

834847
return int(nBig.Int64()), nil
835848
}
849+
850+
// ObfuscateConfig alters the config string by replacing sensitive data with
851+
// random values and updates the privacy pair map. We only substitute items in
852+
// strings, numbers are left unchanged.
853+
func ObfuscateConfig(privacyPairs map[string]string, configB []byte) ([]byte,
854+
error) {
855+
856+
if len(configB) == 0 {
857+
return nil, nil
858+
}
859+
860+
// We assume that the config is a json dict.
861+
var configMap map[string]any
862+
err := json.Unmarshal(configB, &configMap)
863+
if err != nil {
864+
return nil, err
865+
}
866+
867+
// Split the value into a slice of strings.
868+
newConfigMap := make(map[string]any)
869+
for k, v := range configMap {
870+
stringValue, ok := v.(string)
871+
// We only substitute items in strings.
872+
if !ok {
873+
newConfigMap[k] = v
874+
continue
875+
}
876+
877+
// If the value is a string, it is a single entry or a list of
878+
// pubkeys, channel ids or channel points separated by commas.
879+
values := strings.Split(stringValue, ",")
880+
881+
newValues := make([]string, len(values))
882+
for i, value := range values {
883+
value := strings.TrimSpace(value)
884+
885+
// Try to replace with a chan point.
886+
_, _, err := firewalldb.DecodeChannelPoint(value)
887+
888+
if err == nil {
889+
newValue, err := firewalldb.NewPseudoChanPoint()
890+
if err != nil {
891+
return nil, err
892+
}
893+
894+
newValues[i] = newValue
895+
privacyPairs[value] = newValue
896+
897+
continue
898+
}
899+
900+
// If the value is a pubkey, replace it with a random
901+
// value.
902+
_, err = hex.DecodeString(value)
903+
if err == nil && len(value) == pubKeyLen {
904+
newValue, err := firewalldb.NewPseudoStr(
905+
len(value),
906+
)
907+
if err != nil {
908+
return nil, err
909+
}
910+
911+
newValues[i] = newValue
912+
privacyPairs[value] = newValue
913+
914+
continue
915+
}
916+
917+
// If the value is a channel id, replace it with
918+
// a random value.
919+
_, err = strconv.ParseInt(value, 10, 64)
920+
length := len(value)
921+
922+
// Channel ids can have different lenghts depending on
923+
// the blockheight, 20 is equivalent to 10E9 blocks.
924+
if err == nil && minChanIDLen <= length &&
925+
length <= maxChanIDLen {
926+
927+
newValue, err := firewalldb.NewPseudoStr(
928+
len(value),
929+
)
930+
if err != nil {
931+
return nil, err
932+
}
933+
934+
newValues[i] = newValue
935+
privacyPairs[value] = newValue
936+
937+
continue
938+
}
939+
940+
newValues[i] = value
941+
}
942+
v = strings.Join(newValues, ",")
943+
newConfigMap[k] = v
944+
}
945+
946+
// Marshal the map back into a JSON blob.
947+
return json.Marshal(newConfigMap)
948+
}

firewall/privacy_mapper_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,93 @@ func TestHideBool(t *testing.T) {
675675
require.False(t, val)
676676
}
677677

678+
// TestObfuscateConfig tests that we substitute substrings in the config
679+
// correctly.
680+
func TestObfuscateConfig(t *testing.T) {
681+
tests := []struct {
682+
name string
683+
config []byte
684+
expectedReplacements int
685+
sameLengthNotExpected bool
686+
}{
687+
{
688+
name: "empty",
689+
},
690+
{
691+
name: "several pubkeys",
692+
config: []byte(`{"version":1,"pubkeys":` +
693+
`"d23da57575cdcb878ac191e1e0c8a5c4f061b11cfdc7a8ec5c9d495270de66fdbf,` +
694+
`0e092708c9e737115ff14a85b65466561280d77c1b8cd666bc655536ad81ccca85,` +
695+
`DEAD2708c9e737115ff14a85b65466561280d77c1b8cd666bc655536ad81ccca85,` +
696+
`586b59212da4623c40dcc68c4573da1719e5893630790c9f2db8940fff3efd8cd4"}`),
697+
expectedReplacements: 4,
698+
},
699+
{
700+
name: "single pubkey",
701+
config: []byte(`{"version":1,"pubkeys":` +
702+
`"586b59212da4623c40dcc68c4573da1719e5893630790c9f2db8940fff3efd8cd4"}`),
703+
expectedReplacements: 1,
704+
},
705+
{
706+
name: "invalid pubkeys",
707+
config: []byte(`{"version":1,"pubkeys":` +
708+
`"d23da57575cdcb878ac191e1e0c8a5c4f061b11,` +
709+
`586b59212da4623c40dcc68c4573da1719e5893630790c9f2db8940fff3efd8cd4dead,` +
710+
`x86b59212da4623c40dcc68c4573da1719e5893630790c9f2db8940fff3efd8cd4"}`),
711+
expectedReplacements: 0,
712+
},
713+
{
714+
name: "channel ids",
715+
config: []byte(`{"version":1,"channels":` +
716+
`"1,` +
717+
`12345,` +
718+
`1234567890123,` +
719+
`1234567890123456789,` +
720+
`123456789012345678901"}`),
721+
expectedReplacements: 2,
722+
},
723+
{
724+
name: "channel points",
725+
config: []byte(`{"version":1,"channels":"` +
726+
`0e092708c9e737115ff14a85b65466561280d77c1b8cd666bc655536ad81ccca:1,` +
727+
`e092708c9e737115ff14a85b65466561280d77c1b8cd666bc655536ad81ccca:1,` +
728+
`0e092708c9e737115ff14a85b65466561280d77c1b8cd666bc655536ad81ccca3:1,` +
729+
`0e092708c9e737115ff14a85b65466561280d77c1b8cd666bc655536ad81ccca:3000"}`),
730+
expectedReplacements: 2,
731+
sameLengthNotExpected: true,
732+
},
733+
{
734+
name: "number",
735+
config: []byte(`{"version":1,"number":12345678901234567890}`),
736+
expectedReplacements: 0,
737+
},
738+
}
739+
740+
for _, tt := range tests {
741+
tt := tt
742+
743+
t.Run(tt.name, func(t *testing.T) {
744+
privacyPairMap := make(map[string]string)
745+
config, err := ObfuscateConfig(
746+
privacyPairMap, tt.config,
747+
)
748+
require.NoError(t, err)
749+
750+
// We expect the config to be obfuscated only if there
751+
// is sensitive data.
752+
if tt.expectedReplacements > 0 {
753+
require.NotEqual(t, config, tt.config)
754+
}
755+
require.Equal(t, tt.expectedReplacements,
756+
len(privacyPairMap))
757+
758+
if !tt.sameLengthNotExpected {
759+
require.Equal(t, len(tt.config), len(config))
760+
}
761+
})
762+
}
763+
}
764+
678765
// mean computes the mean of the given slice of numbers.
679766
func mean(numbers []uint64) uint64 {
680767
sum := uint64(0)

session_rpcserver.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1015,10 +1015,28 @@ func (s *sessionRpcServer) AddAutopilotSession(ctx context.Context,
10151015
return nil, err
10161016
}
10171017

1018+
// The feature configurations may contain sensitive data like pubkeys,
1019+
// which we replace here and add to to the privacy map.
1020+
obfuscatedConfig := make(map[string][]byte, len(featureConfig))
1021+
if privacy {
1022+
for name, configB := range featureConfig {
1023+
configB, err = firewall.ObfuscateConfig(
1024+
privacyMapPairs, configB,
1025+
)
1026+
if err != nil {
1027+
return nil, err
1028+
}
1029+
1030+
obfuscatedConfig[name] = configB
1031+
}
1032+
} else {
1033+
obfuscatedConfig = featureConfig
1034+
}
1035+
10181036
// Attempt to register the session with the Autopilot server.
10191037
remoteKey, err := s.cfg.autopilot.RegisterSession(
10201038
ctx, sess.LocalPublicKey, sess.ServerAddr, sess.DevServer,
1021-
featureConfig,
1039+
obfuscatedConfig,
10221040
)
10231041
if err != nil {
10241042
return nil, fmt.Errorf("error registering session with "+

0 commit comments

Comments
 (0)