Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 2c78b0d

Browse files
committedNov 13, 2024·
PSK support for v2
1 parent 5380fef commit 2c78b0d

File tree

8 files changed

+458
-33
lines changed

8 files changed

+458
-33
lines changed
 

‎connection_state.go

+7-8
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ type ConnectionState struct {
2727
writeLock sync.Mutex
2828
}
2929

30-
func NewConnectionState(l *logrus.Logger, cs *CertState, crt cert.Certificate, initiator bool, pattern noise.HandshakePattern) (*ConnectionState, error) {
30+
func NewConnectionState(l *logrus.Logger, cs *CertState, crt cert.Certificate, initiator bool, pattern noise.HandshakePattern, psk []byte) (*ConnectionState, error) {
3131
var dhFunc noise.DHFunc
3232
switch crt.Curve() {
3333
case cert.Curve_CURVE25519:
@@ -56,13 +56,12 @@ func NewConnectionState(l *logrus.Logger, cs *CertState, crt cert.Certificate, i
5656
b.Update(l, 0)
5757

5858
hs, err := noise.NewHandshakeState(noise.Config{
59-
CipherSuite: ncs,
60-
Random: rand.Reader,
61-
Pattern: pattern,
62-
Initiator: initiator,
63-
StaticKeypair: static,
64-
//NOTE: These should come from CertState (pki.go) when we finally implement it
65-
PresharedKey: []byte{},
59+
CipherSuite: ncs,
60+
Random: rand.Reader,
61+
Pattern: pattern,
62+
Initiator: initiator,
63+
StaticKeypair: static,
64+
PresharedKey: psk,
6665
PresharedKeyPlacement: 0,
6766
})
6867
if err != nil {

‎e2e/handshakes_test.go

+132
Original file line numberDiff line numberDiff line change
@@ -1105,6 +1105,138 @@ func TestV2NonPrimaryWithLighthouse(t *testing.T) {
11051105
theirControl.Stop()
11061106
}
11071107

1108+
func TestPSK(t *testing.T) {
1109+
tests := []struct {
1110+
name string
1111+
myPskMode nebula.PskMode
1112+
theirPskMode nebula.PskMode
1113+
}{
1114+
// All accepting
1115+
{
1116+
name: "both accepting",
1117+
myPskMode: nebula.PskAccepting,
1118+
theirPskMode: nebula.PskAccepting,
1119+
},
1120+
1121+
// accepting and sending both ways
1122+
{
1123+
name: "accepting to sending",
1124+
myPskMode: nebula.PskAccepting,
1125+
theirPskMode: nebula.PskSending,
1126+
},
1127+
{
1128+
name: "sending to accepting",
1129+
myPskMode: nebula.PskSending,
1130+
theirPskMode: nebula.PskAccepting,
1131+
},
1132+
1133+
// All sending
1134+
{
1135+
name: "sending to sending",
1136+
myPskMode: nebula.PskSending,
1137+
theirPskMode: nebula.PskSending,
1138+
},
1139+
1140+
// enforced and sending both ways
1141+
{
1142+
name: "enforced to sending",
1143+
myPskMode: nebula.PskEnforced,
1144+
theirPskMode: nebula.PskSending,
1145+
},
1146+
{
1147+
name: "sending to enforced",
1148+
myPskMode: nebula.PskSending,
1149+
theirPskMode: nebula.PskEnforced,
1150+
},
1151+
1152+
// All enforced
1153+
{
1154+
name: "both enforced",
1155+
myPskMode: nebula.PskEnforced,
1156+
theirPskMode: nebula.PskEnforced,
1157+
},
1158+
1159+
// Enforced can technically handshake with an accepting node, but it is bad to be in this state
1160+
{
1161+
name: "enforced to accepting",
1162+
myPskMode: nebula.PskEnforced,
1163+
theirPskMode: nebula.PskAccepting,
1164+
},
1165+
}
1166+
1167+
for _, test := range tests {
1168+
t.Run(test.name, func(t *testing.T) {
1169+
var myPskSettings, theirPskSettings m
1170+
1171+
switch test.myPskMode {
1172+
case nebula.PskAccepting:
1173+
myPskSettings = m{"psk": &m{"mode": "accepting", "keys": []string{"garbage0", "this is a key"}}}
1174+
case nebula.PskSending:
1175+
myPskSettings = m{"psk": &m{"mode": "sending", "keys": []string{"this is a key", "garbage1"}}}
1176+
case nebula.PskEnforced:
1177+
myPskSettings = m{"psk": &m{"mode": "enforced", "keys": []string{"this is a key", "garbage2"}}}
1178+
}
1179+
1180+
switch test.theirPskMode {
1181+
case nebula.PskAccepting:
1182+
theirPskSettings = m{"psk": &m{"mode": "accepting", "keys": []string{"garbage3", "this is a key"}}}
1183+
case nebula.PskSending:
1184+
theirPskSettings = m{"psk": &m{"mode": "sending", "keys": []string{"this is a key", "garbage4"}}}
1185+
case nebula.PskEnforced:
1186+
theirPskSettings = m{"psk": &m{"mode": "enforced", "keys": []string{"this is a key", "garbage5"}}}
1187+
}
1188+
1189+
ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, nil)
1190+
myControl, myVpnIp, myUdpAddr, _ := newSimpleServer(cert.Version2, ca, caKey, "me", "10.0.0.1/24", myPskSettings)
1191+
theirControl, theirVpnIp, theirUdpAddr, _ := newSimpleServer(cert.Version2, ca, caKey, "them", "10.0.0.2/24", theirPskSettings)
1192+
1193+
myControl.InjectLightHouseAddr(theirVpnIp[0].Addr(), theirUdpAddr)
1194+
r := router.NewR(t, myControl, theirControl)
1195+
1196+
// Start the servers
1197+
myControl.Start()
1198+
theirControl.Start()
1199+
1200+
t.Log("Route until we see our cached packet flow")
1201+
myControl.InjectTunUDPPacket(theirVpnIp[0].Addr(), 80, myVpnIp[0].Addr(), 80, []byte("Hi from me"))
1202+
r.RouteForAllExitFunc(func(p *udp.Packet, c *nebula.Control) router.ExitType {
1203+
h := &header.H{}
1204+
err := h.Parse(p.Data)
1205+
if err != nil {
1206+
panic(err)
1207+
}
1208+
1209+
// If this is the stage 1 handshake packet and I am configured to send with a psk, my cert name should
1210+
// not appear. It would likely be more obvious to unmarshal the payload and check but this works fine for now
1211+
if test.myPskMode == nebula.PskEnforced || test.myPskMode == nebula.PskSending {
1212+
if h.Type == 0 && h.MessageCounter == 1 {
1213+
assert.NotContains(t, string(p.Data), "test me")
1214+
}
1215+
}
1216+
1217+
if p.To == theirUdpAddr && h.Type == 1 {
1218+
return router.RouteAndExit
1219+
}
1220+
1221+
return router.KeepRouting
1222+
})
1223+
1224+
t.Log("My cached packet should be received by them")
1225+
myCachedPacket := theirControl.GetFromTun(true)
1226+
assertUdpPacket(t, []byte("Hi from me"), myCachedPacket, myVpnIp[0].Addr(), theirVpnIp[0].Addr(), 80, 80)
1227+
1228+
t.Log("Test the tunnel with them")
1229+
assertHostInfoPair(t, myUdpAddr, theirUdpAddr, myVpnIp, theirVpnIp, myControl, theirControl)
1230+
assertTunnel(t, myVpnIp[0].Addr(), theirVpnIp[0].Addr(), myControl, theirControl, r)
1231+
1232+
myControl.Stop()
1233+
theirControl.Stop()
1234+
//TODO: assert hostmaps
1235+
})
1236+
}
1237+
1238+
}
1239+
11081240
//TODO: test
11091241
// Race winner renews and handshakes
11101242
// Race loser renews and handshakes

‎e2e/router/router.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,6 @@ type ExitFunc func(packet *udp.Packet, receiver *nebula.Control) ExitType
111111
func NewR(t testing.TB, controls ...*nebula.Control) *R {
112112
ctx, cancel := context.WithCancel(context.Background())
113113

114-
if err := os.MkdirAll("mermaid", 0755); err != nil {
115-
panic(err)
116-
}
117-
118114
r := &R{
119115
controls: make(map[netip.AddrPort]*nebula.Control),
120116
vpnControls: make(map[netip.Addr]*nebula.Control),
@@ -194,6 +190,9 @@ func (r *R) renderFlow() {
194190
return
195191
}
196192

193+
if err := os.MkdirAll(filepath.Dir(r.fn), 0755); err != nil {
194+
panic(err)
195+
}
197196
f, err := os.OpenFile(r.fn, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0644)
198197
if err != nil {
199198
panic(err)

‎examples/config.yml

+32-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,38 @@ pki:
1919
# After all hosts in the mesh are using a v2 certificate then v1 certificates are no longer needed.
2020
# default_version: 1
2121

22+
# psk can be used to mask the contents of handshakes.
23+
psk:
24+
# `mode` defines how the pre shared keys can be used in a handshake.
25+
# `accepting` (the default) will initiate handshakes using an empty key and will try to use any keys provided when
26+
# receiving handshakes, including an empty key.
27+
# `sending` will initiate handshakes with the first key provided and will try to use any keys provided when
28+
# receiving handshakes, including an empty key.
29+
# `enforced` will initiate handshakes with the first psk key provided and will try to use any keys provided when
30+
# responding to handshakes. An empty key will not be allowed.
31+
#
32+
# To change a mesh from not using a psk to enforcing psk:
33+
# 1. Leave `mode` as `accepting` and configure `psk.keys` to match on all nodes in the mesh and reload.
34+
# 2. Change `mode` to `sending` on all nodes in the mesh and reload.
35+
# 3. Change `mode` to `enforced` on all nodes in the mesh and reload.
36+
#mode: accepting
37+
38+
# The keys provided are sent through hkdf to ensure the shared secret used in the noise protocol is the
39+
# correct byte length.
40+
#
41+
# Only the first key is used for outbound handshakes but all keys provided will be tried in the order specified, on
42+
# incoming handshakes. This is to allow for psk rotation.
43+
#
44+
# To rotate a primary key:
45+
# 1. Put the new key in the 2nd slot on every node in the mesh and reload.
46+
# 2. Move the key from the 2nd slot to the 1st slot, the old primary key is now in the 2nd slot, reload.
47+
# 3. Remove the old primary key once it is no longer in use on every node in the mesh and reload.
48+
#keys:
49+
# - shared secret string, this one is used in all outbound handshakes # This is the primary key used when sending handshakes
50+
# - this is a fallback key, received handshakes can use this
51+
# - another fallback, received handshakes can use this one too
52+
# - "\x68\x65\x6c\x6c\x6f\x20\x66\x72\x69\x65\x6e\x64\x73" # for raw bytes if you desire
53+
2254
# The static host map defines a set of hosts with fixed IP addresses on the internet (or any network).
2355
# A host can have multiple fixed IP addresses defined here, and nebula will try each when establishing a tunnel.
2456
# The syntax is:
@@ -309,7 +341,6 @@ logging:
309341
# after receiving the response for lighthouse queries
310342
#trigger_buffer: 64
311343

312-
313344
# Nebula security group configuration
314345
firewall:
315346
# Action to take when a packet is not allowed by the firewall rules.

‎handshake_ix.go

+39-20
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func ixHandshakeStage0(f *Interface, hh *HandshakeHostInfo) bool {
5050
Error("Unable to handshake with host because no certificate handshake bytes is available")
5151
}
5252

53-
ci, err := NewConnectionState(f.l, cs, crt, true, noise.HandshakeIX)
53+
ci, err := NewConnectionState(f.l, cs, crt, true, noise.HandshakeIX, cs.psk.primary)
5454
if err != nil {
5555
f.l.WithError(err).WithField("vpnAddrs", hh.hostinfo.vpnAddrs).
5656
WithField("handshake", m{"stage": 0, "style": "ix_psk0"}).
@@ -104,34 +104,53 @@ func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet
104104
Error("Unable to handshake with host because no certificate is available")
105105
}
106106

107-
ci, err := NewConnectionState(f.l, cs, crt, false, noise.HandshakeIX)
108-
if err != nil {
109-
f.l.WithError(err).WithField("udpAddr", addr).
110-
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
111-
Error("Failed to create connection state")
112-
return
113-
}
107+
var (
108+
err error
109+
ci *ConnectionState
110+
msg []byte
111+
)
114112

115-
// Mark packet 1 as seen so it doesn't show up as missed
116-
ci.window.Update(f.l, 1)
113+
hs := &NebulaHandshake{}
117114

118-
msg, _, _, err := ci.H.ReadMessage(nil, packet[header.Len:])
119-
if err != nil {
120-
f.l.WithError(err).WithField("udpAddr", addr).
121-
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
122-
Error("Failed to call noise.ReadMessage")
123-
return
115+
for _, psk := range cs.psk.keys {
116+
ci, err = NewConnectionState(f.l, cs, crt, false, noise.HandshakeIX, psk)
117+
if err != nil {
118+
//TODO: should be bother logging this, if we have multiple psks and the error is unrelated it will be verbose.
119+
f.l.WithError(err).WithField("udpAddr", addr).
120+
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
121+
Error("Failed to create connection state")
122+
continue
123+
}
124+
125+
msg, _, _, err = ci.H.ReadMessage(nil, packet[header.Len:])
126+
if err != nil {
127+
// Calls to ReadMessage with an incorrect psk should fail, try the next one if we have one
128+
continue
129+
}
130+
131+
// Sometimes ReadMessage returns fine with a nil psk even if the handshake is using a psk, ensure our protobuf
132+
// comes out clean as well
133+
err = hs.Unmarshal(msg)
134+
if err == nil {
135+
// There was no error, we can continue with this handshake
136+
break
137+
}
138+
139+
// The unmarshal failed, try the next psk if we have one
124140
}
125141

126-
hs := &NebulaHandshake{}
127-
err = hs.Unmarshal(msg)
142+
// We finished with an error, log it and get out
128143
if err != nil || hs.Details == nil {
129-
f.l.WithError(err).WithField("udpAddr", addr).
144+
// We aren't logging the error here because we can't be sure of the failure when using psk
145+
f.l.WithField("udpAddr", addr).
130146
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
131-
Error("Failed unmarshal handshake message")
147+
Error("Was unable to decrypt the handshake")
132148
return
133149
}
134150

151+
// Mark packet 1 as seen so it doesn't show up as missed
152+
ci.window.Update(f.l, 1)
153+
135154
remoteCert, err := cert.RecombineAndValidate(cert.Version(hs.Details.CertVersion), hs.Details.Cert, ci.H.PeerStatic(), ci.Curve(), f.pki.GetCAPool())
136155
if err != nil {
137156
e := f.l.WithError(err).WithField("udpAddr", addr).

‎pki.go

+20
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ type CertState struct {
3838
pkcs11Backed bool
3939
cipher string
4040

41+
psk *Psk
42+
4143
myVpnNetworks []netip.Prefix
4244
myVpnNetworksTable *bart.Table[struct{}]
4345
myVpnAddrs []netip.Addr
@@ -97,6 +99,14 @@ func (p *PKI) reload(c *config.C, initial bool) error {
9799
err.Log(p.l)
98100
}
99101

102+
err = p.reloadCAPool(c)
103+
if err != nil {
104+
if initial {
105+
return err
106+
}
107+
err.Log(p.l)
108+
}
109+
100110
return nil
101111
}
102112

@@ -181,6 +191,16 @@ func (p *PKI) reloadCerts(c *config.C, initial bool) *util.ContextualError {
181191
}
182192
}
183193

194+
psk, err := NewPskFromConfig(c)
195+
if err != nil {
196+
return util.NewContextualError("Failed to load psk from config", nil, err)
197+
}
198+
if len(psk.keys) > 0 {
199+
p.l.WithField("pskMode", psk.mode).WithField("keysLen", len(psk.keys)).
200+
Info("pre shared keys are in use")
201+
}
202+
newState.psk = psk
203+
184204
p.cs.Store(newState)
185205

186206
//TODO: newState needs a stringer that does json

0 commit comments

Comments
 (0)
Please sign in to comment.