Skip to content
This repository has been archived by the owner on Aug 19, 2022. It is now read-only.

use TLS session resumption #69

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cmd/tlsdiag/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"time"

"github.com/libp2p/go-libp2p-core/peer"
"github.com/libp2p/go-libp2p-peerstore/pstoremem"
libp2ptls "github.com/libp2p/go-libp2p-tls"
)

Expand All @@ -33,7 +34,7 @@ func StartClient() error {
return err
}
fmt.Printf(" Peer ID: %s\n", id.Pretty())
tp, err := libp2ptls.New(priv)
tp, err := libp2ptls.New(priv, pstoremem.NewPeerstore())
if err != nil {
return err
}
Expand Down
3 changes: 2 additions & 1 deletion cmd/tlsdiag/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"time"

"github.com/libp2p/go-libp2p-core/peer"
"github.com/libp2p/go-libp2p-peerstore/pstoremem"
libp2ptls "github.com/libp2p/go-libp2p-tls"
)

Expand All @@ -26,7 +27,7 @@ func StartServer() error {
return err
}
fmt.Printf(" Peer ID: %s\n", id.Pretty())
tp, err := libp2ptls.New(priv)
tp, err := libp2ptls.New(priv, pstoremem.NewPeerstore())
if err != nil {
return err
}
Expand Down
3 changes: 2 additions & 1 deletion crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
const certValidityPeriod = 100 * 365 * 24 * time.Hour // ~100 years
const certificatePrefix = "libp2p-tls-handshake:"
const alpn string = "libp2p"
const peerStoreKey = "libp2p-tls-session-cache"

var extensionID = getPrefixedExtensionID([]int{1, 1})

Expand Down Expand Up @@ -52,7 +53,7 @@ func NewIdentity(privKey ic.PrivKey) (*Identity, error) {
panic("tls config not specialized for peer")
},
NextProtos: []string{alpn},
SessionTicketsDisabled: true,
SessionTicketsDisabled: false,
},
}, nil
}
Expand Down
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ module github.com/libp2p/go-libp2p-tls
go 1.14

require (
github.com/libp2p/go-libp2p-core v0.3.0
github.com/libp2p/go-libp2p-core v0.6.1
github.com/libp2p/go-libp2p-peerstore v0.2.6
github.com/onsi/ginkgo v1.12.0
github.com/onsi/gomega v1.9.0
golang.org/x/sys v0.0.0-20191206220618-eeba5f6aabab
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5
)
188 changes: 127 additions & 61 deletions go.sum

Large diffs are not rendered by default.

51 changes: 51 additions & 0 deletions session_cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package libp2ptls

import (
"crypto/tls"

ci "github.com/libp2p/go-libp2p-core/crypto"
)

const cacheSize = 3

type clientSessionCache struct {
cache []*tls.ClientSessionState

// When using session resumption, the server won't send its certificate chain.
// We therefore need to save its public key when storing a session ticket,
// so we can return it on conn.RemotePublicKey().
pubKey ci.PubKey
}

var _ tls.ClientSessionCache = &clientSessionCache{}

func newClientSessionCache() *clientSessionCache {
return &clientSessionCache{}
}

func (c *clientSessionCache) Put(_ string, cs *tls.ClientSessionState) {
if len(c.cache) == cacheSize {
c.cache = c.cache[1:]
}
c.cache = append(c.cache, cs)
}

func (c *clientSessionCache) Get(_ string) (*tls.ClientSessionState, bool) {
if len(c.cache) == 0 {
return nil, false
}
ticket := c.cache[len(c.cache)-1]
c.cache = c.cache[:len(c.cache)-1]
return ticket, true
}

func (c *clientSessionCache) SetPubKey(k ci.PubKey) {
if c.pubKey != nil && !c.pubKey.Equals(k) {
panic("mismatching public key")
}
c.pubKey = k
}

func (c *clientSessionCache) GetPubKey() ci.PubKey {
return c.pubKey
}
99 changes: 99 additions & 0 deletions session_cache_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package libp2ptls

import (
"crypto/rand"
"crypto/tls"
"encoding/binary"
"unsafe"

ci "github.com/libp2p/go-libp2p-core/crypto"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

var _ = Describe("Session Ticket Cache", func() {
var cache *clientSessionCache
const key = "irrelevant"
ticketSize := unsafe.Sizeof(&tls.ClientSessionState{})

toSessionTicket := func(n int) *tls.ClientSessionState {
b := make([]byte, ticketSize)
binary.BigEndian.PutUint32(b, uint32(n))
return (*tls.ClientSessionState)(unsafe.Pointer(&b))
}

fromSessionTicket := func(t *tls.ClientSessionState) int {
b := (*[]byte)(unsafe.Pointer(t))
return int(binary.BigEndian.Uint32(*b))
}

BeforeEach(func() {
cache = newClientSessionCache()
})

It("encodes and decodes values from session tickets", func() {
Expect(fromSessionTicket(toSessionTicket(1337))).To(Equal(1337))
})

It("doesn't return a session ticket if there's none", func() {
t, ok := cache.Get(key)
Expect(ok).To(BeFalse())
Expect(t).To(BeNil())
})

It("saves and retrieves session tickets", func() {
cache.Put(key, toSessionTicket(42))
ticket, ok := cache.Get(key)
Expect(ok).To(BeTrue())
Expect(fromSessionTicket(ticket)).To(Equal(42))
_, ok = cache.Get(key)
Expect(ok).To(BeFalse())
})

It("returns the most recent ticket first", func() {
cache.Put(key, toSessionTicket(1))
cache.Put(key, toSessionTicket(2))
ticket, ok := cache.Get(key)
Expect(ok).To(BeTrue())
Expect(fromSessionTicket(ticket)).To(Equal(2))
ticket, ok = cache.Get(key)
Expect(ok).To(BeTrue())
Expect(fromSessionTicket(ticket)).To(Equal(1))
})

It("limits the number of tickets saved", func() {
Expect(cacheSize).To(Equal(3))
cache.Put(key, toSessionTicket(1))
cache.Put(key, toSessionTicket(2))
cache.Put(key, toSessionTicket(3))
cache.Put(key, toSessionTicket(4))
ticket, ok := cache.Get(key)
Expect(ok).To(BeTrue())
Expect(fromSessionTicket(ticket)).To(Equal(4))
ticket, ok = cache.Get(key)
Expect(ok).To(BeTrue())
Expect(fromSessionTicket(ticket)).To(Equal(3))
ticket, ok = cache.Get(key)
Expect(ok).To(BeTrue())
Expect(fromSessionTicket(ticket)).To(Equal(2))
_, ok = cache.Get(key)
Expect(ok).To(BeFalse())
})

It("sets and gets the public key", func() {
_, pub, err := ci.GenerateEd25519Key(rand.Reader)
Expect(err).ToNot(HaveOccurred())
cache.SetPubKey(pub)
Expect(cache.GetPubKey()).To(Equal(pub))
})

It("doesn't allow setting of different public keys", func() {
_, pub1, err := ci.GenerateEd25519Key(rand.Reader)
Expect(err).ToNot(HaveOccurred())
_, pub2, err := ci.GenerateEd25519Key(rand.Reader)
Expect(err).ToNot(HaveOccurred())
cache.SetPubKey(pub1)
Expect(func() { cache.SetPubKey(pub2) }).To(Panic())
})
})
43 changes: 38 additions & 5 deletions transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"sync"

ci "github.com/libp2p/go-libp2p-core/crypto"
"github.com/libp2p/go-libp2p-core/peer"
ps "github.com/libp2p/go-libp2p-core/peerstore"
"github.com/libp2p/go-libp2p-core/sec"
)

Expand All @@ -17,20 +19,22 @@ const ID = "/tls/1.0.0"

// Transport constructs secure communication sessions for a peer.
type Transport struct {
identity *Identity
identity *Identity
peerstore ps.Peerstore

localPeer peer.ID
privKey ci.PrivKey
}

// New creates a TLS encrypted transport
func New(key ci.PrivKey) (*Transport, error) {
func New(key ci.PrivKey, peerstore ps.Peerstore) (*Transport, error) {
id, err := peer.IDFromPrivateKey(key)
if err != nil {
return nil, err
}
t := &Transport{
localPeer: id,
peerstore: peerstore,
privKey: key,
}

Expand All @@ -47,7 +51,7 @@ var _ sec.SecureTransport = &Transport{}
// SecureInbound runs the TLS handshake as a server.
func (t *Transport) SecureInbound(ctx context.Context, insecure net.Conn) (sec.SecureConn, error) {
config, keyCh := t.identity.ConfigForAny()
cs, err := t.handshake(ctx, tls.Server(insecure, config), keyCh)
cs, err := t.handshake(ctx, tls.Server(insecure, config), keyCh, nil)
if err != nil {
insecure.Close()
}
Expand All @@ -63,9 +67,28 @@ func (t *Transport) SecureInbound(ctx context.Context, insecure net.Conn) (sec.S
// notice this after 1 RTT when calling Read.
func (t *Transport) SecureOutbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, error) {
config, keyCh := t.identity.ConfigForPeer(p)
cs, err := t.handshake(ctx, tls.Client(insecure, config), keyCh)

var sessionCache *clientSessionCache
if csc, err := t.peerstore.Get(p, peerStoreKey); err != nil {
if err != ps.ErrNotFound {
panic(fmt.Sprintf("Failed to get session cache from peer store: %s", err))
}
sessionCache = newClientSessionCache()
t.peerstore.Put(p, peerStoreKey, sessionCache)
} else {
sessionCache = csc.(*clientSessionCache)
}
config.ClientSessionCache = sessionCache

cs, err := t.handshake(ctx, tls.Client(insecure, config), keyCh, sessionCache)
if err != nil {
insecure.Close()
} else {
if peerID, err := peer.IDFromPublicKey(cs.RemotePublicKey()); err != nil || peerID != p {
// Should never happen, but make sure that the public key actually matches the peer ID.
// Especially important for resumed connection.
return nil, errors.New("libp2p-tls BUG: peer ID doesn't match public key")
}
}
return cs, err
}
Expand All @@ -74,6 +97,7 @@ func (t *Transport) handshake(
ctx context.Context,
tlsConn *tls.Conn,
keyCh <-chan ci.PubKey,
sessionCache *clientSessionCache,
) (sec.SecureConn, error) {
// There's no way to pass a context to tls.Conn.Handshake().
// See https://github.com/golang/go/issues/18482.
Expand Down Expand Up @@ -115,10 +139,19 @@ func (t *Transport) handshake(
var remotePubKey ci.PubKey
select {
case remotePubKey = <-keyCh:
// In the case of a normal (non-resumed) handshake, the server will send its certificate,
// and we extract its public key from it.
default:
// In the case of a resumed handshake, the server doesn't send any certificate.
// We already know its public key from the last connection.
}
if remotePubKey == nil {
return nil, errors.New("go-libp2p-tls BUG: expected remote pub key to be set")
if !tlsConn.ConnectionState().DidResume || sessionCache == nil {
return nil, errors.New("go-libp2p-tls BUG: expected remote pub key to be set")
}
remotePubKey = sessionCache.GetPubKey()
} else if sessionCache != nil {
sessionCache.SetPubKey(remotePubKey)
}

conn, err := t.setupConn(tlsConn, remotePubKey)
Expand Down
Loading