Skip to content

Commit 4bd8a5c

Browse files
committed
session: add GroupID field to Session
Add the new `GroupID1` field to the `Session` struct and ensure that the new fields are properly serialised and deserialised. The `GroupID` is the ID of the first Session in a set of linked sessions.
1 parent c9c396d commit 4bd8a5c

File tree

6 files changed

+230
-16
lines changed

6 files changed

+230
-16
lines changed

session/interface.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ type Session struct {
6363
RemotePublicKey *btcec.PublicKey
6464
FeatureConfig *FeaturesConfig
6565
WithPrivacyMapper bool
66+
67+
// GroupID is the Session ID of the very first Session in the linked
68+
// group of sessions. If this is the very first session in the group
69+
// then this will be the same as ID.
70+
GroupID ID
6671
}
6772

6873
// MacaroonBaker is a function type for baking a super macaroon.
@@ -73,7 +78,7 @@ type MacaroonBaker func(ctx context.Context, rootKeyID uint64,
7378
func NewSession(id ID, localPrivKey *btcec.PrivateKey, label string, typ Type,
7479
expiry time.Time, serverAddr string, devServer bool, perms []bakery.Op,
7580
caveats []macaroon.Caveat, featureConfig FeaturesConfig,
76-
privacy bool) (*Session, error) {
81+
privacy bool, linkedGroupID *ID) (*Session, error) {
7782

7883
_, pairingSecret, err := mailbox.NewPassphraseEntropy()
7984
if err != nil {
@@ -82,6 +87,15 @@ func NewSession(id ID, localPrivKey *btcec.PrivateKey, label string, typ Type,
8287

8388
macRootKey := NewSuperMacaroonRootKeyID(id)
8489

90+
// The group ID will by default be the same as the Session ID
91+
// unless this session links to a previous session.
92+
groupID := id
93+
if linkedGroupID != nil {
94+
// If this session is linked to a previous session, then the
95+
// group ID is the same as the linked session's group ID.
96+
groupID = *linkedGroupID
97+
}
98+
8599
sess := &Session{
86100
ID: id,
87101
Label: label,
@@ -97,6 +111,7 @@ func NewSession(id ID, localPrivKey *btcec.PrivateKey, label string, typ Type,
97111
LocalPublicKey: localPrivKey.PubKey(),
98112
RemotePublicKey: nil,
99113
WithPrivacyMapper: privacy,
114+
GroupID: groupID,
100115
}
101116

102117
if perms != nil || caveats != nil {

session/store.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,19 @@ func (db *DB) CreateSession(session *Session) error {
6767
session.LocalPublicKey.SerializeCompressed())
6868
}
6969

70+
// If this is a linked session (meaning the group ID is
71+
// different from the ID) the make sure that the Group ID of
72+
// this session is an ID known by the store. We can do this by
73+
// checking that an entry for this ID exists in the id-to-key
74+
// index.
75+
if session.ID != session.GroupID {
76+
_, err = getKeyForID(sessionBucket, session.GroupID)
77+
if err != nil {
78+
return fmt.Errorf("unknown linked session "+
79+
"%x: %w", session.GroupID, err)
80+
}
81+
}
82+
7083
// Add the mapping from session ID to session key to the ID
7184
// index.
7285
err = addIDToKeyPair(sessionBucket, session.ID, sessionKey)

session/store_test.go

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ func TestBasicSessionStore(t *testing.T) {
1919
})
2020

2121
// Create a few sessions.
22-
s1 := newSession(t, db, "session 1")
23-
s2 := newSession(t, db, "session 2")
24-
s3 := newSession(t, db, "session 3")
25-
s4 := newSession(t, db, "session 3")
22+
s1 := newSession(t, db, "session 1", nil)
23+
s2 := newSession(t, db, "session 2", nil)
24+
s3 := newSession(t, db, "session 3", nil)
25+
s4 := newSession(t, db, "session 4", nil)
2626

2727
// Persist session 1. This should now succeed.
2828
require.NoError(t, db.CreateSession(s1))
@@ -34,6 +34,7 @@ func TestBasicSessionStore(t *testing.T) {
3434
// Change the local pub key of session 4 such that it has the same
3535
// ID as session 1.
3636
s4.ID = s1.ID
37+
s4.GroupID = s1.GroupID
3738

3839
// Now try to insert session 4. This should fail due to an entry for
3940
// the ID already existing.
@@ -84,14 +85,41 @@ func TestBasicSessionStore(t *testing.T) {
8485
require.Equal(t, session1.State, StateRevoked)
8586
}
8687

87-
func newSession(t *testing.T, db Store, label string) *Session {
88+
// TestLinkingSessions tests that session linking works as expected.
89+
func TestLinkingSessions(t *testing.T) {
90+
// Set up a new DB.
91+
db, err := NewDB(t.TempDir(), "test.db")
92+
require.NoError(t, err)
93+
t.Cleanup(func() {
94+
_ = db.Close()
95+
})
96+
97+
// Create a new session with no previous link.
98+
s1 := newSession(t, db, "session 1", nil)
99+
100+
// Create another session and link it to the first.
101+
s2 := newSession(t, db, "session 2", &s1.GroupID)
102+
103+
// Try to persist the second session and assert that it fails due to the
104+
// linked session not existing in the DB yet.
105+
require.ErrorContains(t, db.CreateSession(s2), "unknown linked session")
106+
107+
// Now persist the first session and retry persisting the second one
108+
// and assert that this now works.
109+
require.NoError(t, db.CreateSession(s1))
110+
require.NoError(t, db.CreateSession(s2))
111+
}
112+
113+
func newSession(t *testing.T, db Store, label string,
114+
linkedGroupID *ID) *Session {
115+
88116
id, priv, err := db.GetUnusedIDAndKeyPair()
89117
require.NoError(t, err)
90118

91119
session, err := NewSession(
92120
id, priv, label, TypeMacaroonAdmin,
93121
time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC),
94-
"foo.bar.baz:1234", true, nil, nil, nil, true,
122+
"foo.bar.baz:1234", true, nil, nil, nil, true, linkedGroupID,
95123
)
96124
require.NoError(t, err)
97125

session/tlv.go

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const (
2828
typeFeaturesConfig tlv.Type = 14
2929
typeWithPrivacy tlv.Type = 15
3030
typeRevokedAt tlv.Type = 16
31+
typeGroupID tlv.Type = 17
3132

3233
// typeMacaroon is no longer used, but we leave it defined for backwards
3334
// compatibility.
@@ -54,6 +55,28 @@ func SerializeSession(w io.Writer, session *Session) error {
5455
return fmt.Errorf("session cannot be nil")
5556
}
5657

58+
tlvRecords, err := constructSessionTLVRecords(session, true)
59+
if err != nil {
60+
return err
61+
}
62+
63+
tlvStream, err := tlv.NewStream(tlvRecords...)
64+
if err != nil {
65+
return err
66+
}
67+
68+
return tlvStream.Encode(w)
69+
}
70+
71+
// constructSessionTlvRecords takes a Session and gathers all the TLV records
72+
// that need to be serialised for the session. The withGroupID boolean can be
73+
// used to exclude the tlv record for the GroupID of the session. This should
74+
// only ever be set to true for testing purposes to ensure that older sessions
75+
// which did do not have the GroupID serialised still correctly deserialize and
76+
// set the correct GroupID in the Session struct.
77+
func constructSessionTLVRecords(session *Session, withGroupID bool) (
78+
[]tlv.Record, error) {
79+
5780
var (
5881
label = []byte(session.Label)
5982
state = uint8(session.State)
@@ -140,12 +163,14 @@ func SerializeSession(w io.Writer, session *Session) error {
140163
tlv.MakePrimitiveRecord(typeRevokedAt, &revokedAt),
141164
)
142165

143-
tlvStream, err := tlv.NewStream(tlvRecords...)
144-
if err != nil {
145-
return err
166+
if withGroupID {
167+
groupID := session.GroupID[:]
168+
tlvRecords = append(tlvRecords, tlv.MakePrimitiveRecord(
169+
typeGroupID, &groupID,
170+
))
146171
}
147172

148-
return tlvStream.Encode(w)
173+
return tlvRecords, nil
149174
}
150175

151176
// DeserializeSession deserializes a session from the given reader, expecting
@@ -159,6 +184,7 @@ func DeserializeSession(r io.Reader) (*Session, error) {
159184
expiry, createdAt, revokedAt uint64
160185
macRecipe MacaroonRecipe
161186
featureConfig FeaturesConfig
187+
groupID []byte
162188
)
163189
tlvStream, err := tlv.NewStream(
164190
tlv.MakePrimitiveRecord(typeLabel, &label),
@@ -186,6 +212,7 @@ func DeserializeSession(r io.Reader) (*Session, error) {
186212
),
187213
tlv.MakePrimitiveRecord(typeWithPrivacy, &privacy),
188214
tlv.MakePrimitiveRecord(typeRevokedAt, &revokedAt),
215+
tlv.MakePrimitiveRecord(typeGroupID, &groupID),
189216
)
190217
if err != nil {
191218
return nil, err
@@ -228,6 +255,15 @@ func DeserializeSession(r io.Reader) (*Session, error) {
228255
session.FeatureConfig = &featureConfig
229256
}
230257

258+
// The GroupID field might not be set for older sessions. So we attempt
259+
// to read it from the DB, otherwise we set the group ID to the session
260+
// ID.
261+
if t, ok := parsedTypes[typeGroupID]; ok && t == nil {
262+
copy(session.GroupID[:], groupID)
263+
} else {
264+
session.GroupID = session.ID
265+
}
266+
231267
return session, nil
232268
}
233269

0 commit comments

Comments
 (0)