transport_test.go 7.49 KB
Newer Older
Marten Seemann's avatar
Marten Seemann committed
1 2 3 4
package libp2ptls

import (
	"context"
Marten Seemann's avatar
Marten Seemann committed
5 6
	"crypto/ecdsa"
	"crypto/elliptic"
Marten Seemann's avatar
Marten Seemann committed
7 8
	"crypto/rand"
	"crypto/rsa"
Marten Seemann's avatar
Marten Seemann committed
9 10
	"fmt"
	mrand "math/rand"
Marten Seemann's avatar
Marten Seemann committed
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
	"net"

	cs "github.com/libp2p/go-conn-security"
	ic "github.com/libp2p/go-libp2p-crypto"
	peer "github.com/libp2p/go-libp2p-peer"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Transport", func() {
	var (
		serverKey, clientKey ic.PrivKey
		serverID, clientID   peer.ID
	)

	createPeer := func() (peer.ID, ic.PrivKey) {
Marten Seemann's avatar
Marten Seemann committed
27 28 29 30 31 32 33 34 35 36 37 38
		var priv ic.PrivKey
		if mrand.Int()%2 == 0 {
			fmt.Fprintln(GinkgoWriter, " using an ECDSA key")
			var err error
			priv, _, err = ic.GenerateECDSAKeyPair(rand.Reader)
			Expect(err).ToNot(HaveOccurred())
		} else {
			fmt.Fprintln(GinkgoWriter, " using an RSA key")
			var err error
			priv, _, err = ic.GenerateRSAKeyPair(1024, rand.Reader)
			Expect(err).ToNot(HaveOccurred())
		}
Marten Seemann's avatar
Marten Seemann committed
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
		id, err := peer.IDFromPrivateKey(priv)
		Expect(err).ToNot(HaveOccurred())
		return id, priv
	}

	connect := func() (net.Conn, net.Conn) {
		ln, err := net.Listen("tcp", "localhost:0")
		Expect(err).ToNot(HaveOccurred())
		defer ln.Close()
		serverConnChan := make(chan net.Conn)
		go func() {
			defer GinkgoRecover()
			conn, err := ln.Accept()
			Expect(err).ToNot(HaveOccurred())
			serverConnChan <- conn
		}()
		conn, err := net.Dial("tcp", ln.Addr().String())
		Expect(err).ToNot(HaveOccurred())
		return conn, <-serverConnChan
	}

	// modify the cert chain such that verificiation will fail
	invalidateCertChain := func(identity *Identity) {
Marten Seemann's avatar
Marten Seemann committed
62 63 64 65 66 67 68 69 70 71 72 73
		switch identity.Config.Certificates[0].PrivateKey.(type) {
		case *rsa.PrivateKey:
			key, err := rsa.GenerateKey(rand.Reader, 1024)
			Expect(err).ToNot(HaveOccurred())
			identity.Config.Certificates[0].PrivateKey = key
		case *ecdsa.PrivateKey:
			key, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
			Expect(err).ToNot(HaveOccurred())
			identity.Config.Certificates[0].PrivateKey = key
		default:
			Fail("unexpected private key type")
		}
Marten Seemann's avatar
Marten Seemann committed
74 75 76
	}

	BeforeEach(func() {
Marten Seemann's avatar
Marten Seemann committed
77
		fmt.Fprintf(GinkgoWriter, "Initializing a server")
Marten Seemann's avatar
Marten Seemann committed
78
		serverID, serverKey = createPeer()
Marten Seemann's avatar
Marten Seemann committed
79
		fmt.Fprintf(GinkgoWriter, "Initializing a client")
Marten Seemann's avatar
Marten Seemann committed
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
		clientID, clientKey = createPeer()
	})

	It("handshakes", func() {
		clientTransport, err := New(clientKey)
		Expect(err).ToNot(HaveOccurred())
		serverTransport, err := New(serverKey)
		Expect(err).ToNot(HaveOccurred())

		clientInsecureConn, serverInsecureConn := connect()

		serverConnChan := make(chan cs.Conn)
		go func() {
			defer GinkgoRecover()
			serverConn, err := serverTransport.SecureInbound(context.Background(), serverInsecureConn)
			Expect(err).ToNot(HaveOccurred())
			serverConnChan <- serverConn
		}()
		clientConn, err := clientTransport.SecureOutbound(context.Background(), clientInsecureConn, serverID)
		Expect(err).ToNot(HaveOccurred())
		var serverConn cs.Conn
		Eventually(serverConnChan).Should(Receive(&serverConn))
		defer clientConn.Close()
		defer serverConn.Close()
		Expect(clientConn.LocalPeer()).To(Equal(clientID))
		Expect(serverConn.LocalPeer()).To(Equal(serverID))
		Expect(clientConn.LocalPrivateKey()).To(Equal(clientKey))
		Expect(serverConn.LocalPrivateKey()).To(Equal(serverKey))
		Expect(clientConn.RemotePeer()).To(Equal(serverID))
		Expect(serverConn.RemotePeer()).To(Equal(clientID))
		Expect(clientConn.RemotePublicKey()).To(Equal(serverKey.GetPublic()))
		Expect(serverConn.RemotePublicKey()).To(Equal(clientKey.GetPublic()))
		// exchange some data
		_, err = serverConn.Write([]byte("foobar"))
		Expect(err).ToNot(HaveOccurred())
		b := make([]byte, 6)
		_, err = clientConn.Read(b)
		Expect(err).ToNot(HaveOccurred())
		Expect(string(b)).To(Equal("foobar"))
	})

121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
	It("fails when the context of the outgoing connection is canceled", func() {
		clientTransport, err := New(clientKey)
		Expect(err).ToNot(HaveOccurred())
		serverTransport, err := New(serverKey)
		Expect(err).ToNot(HaveOccurred())

		clientInsecureConn, serverInsecureConn := connect()

		go func() {
			defer GinkgoRecover()
			_, err := serverTransport.SecureInbound(context.Background(), serverInsecureConn)
			Expect(err).To(HaveOccurred())
		}()
		ctx, cancel := context.WithCancel(context.Background())
		cancel()
		_, err = clientTransport.SecureOutbound(ctx, clientInsecureConn, serverID)
137
		Expect(err).To(MatchError(context.Canceled))
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
	})

	It("fails when the context of the incoming connection is canceled", func() {
		clientTransport, err := New(clientKey)
		Expect(err).ToNot(HaveOccurred())
		serverTransport, err := New(serverKey)
		Expect(err).ToNot(HaveOccurred())

		clientInsecureConn, serverInsecureConn := connect()

		go func() {
			defer GinkgoRecover()
			ctx, cancel := context.WithCancel(context.Background())
			cancel()
			_, err := serverTransport.SecureInbound(ctx, serverInsecureConn)
153
			Expect(err).To(MatchError(context.Canceled))
154 155 156 157 158
		}()
		_, err = clientTransport.SecureOutbound(context.Background(), clientInsecureConn, serverID)
		Expect(err).To(HaveOccurred())
	})

Marten Seemann's avatar
Marten Seemann committed
159
	It("fails if the peer ID doesn't match", func() {
Marten Seemann's avatar
Marten Seemann committed
160
		fmt.Fprintf(GinkgoWriter, "Creating another peer")
Marten Seemann's avatar
Marten Seemann committed
161 162 163 164 165 166 167 168 169
		thirdPartyID, _ := createPeer()

		serverTransport, err := New(serverKey)
		Expect(err).ToNot(HaveOccurred())
		clientTransport, err := New(clientKey)
		Expect(err).ToNot(HaveOccurred())

		clientInsecureConn, serverInsecureConn := connect()

Marten Seemann's avatar
Marten Seemann committed
170
		done := make(chan struct{})
Marten Seemann's avatar
Marten Seemann committed
171 172 173 174 175
		go func() {
			defer GinkgoRecover()
			_, err := serverTransport.SecureInbound(context.Background(), serverInsecureConn)
			Expect(err).To(HaveOccurred())
			Expect(err.Error()).To(ContainSubstring("tls: bad certificate"))
Marten Seemann's avatar
Marten Seemann committed
176
			close(done)
Marten Seemann's avatar
Marten Seemann committed
177 178 179 180
		}()
		// dial, but expect the wrong peer ID
		_, err = clientTransport.SecureOutbound(context.Background(), clientInsecureConn, thirdPartyID)
		Expect(err).To(MatchError("peer IDs don't match"))
Marten Seemann's avatar
Marten Seemann committed
181
		Eventually(done).Should(BeClosed())
Marten Seemann's avatar
Marten Seemann committed
182 183 184 185 186 187 188 189 190 191 192
	})

	It("fails if the client presents an invalid cert chain", func() {
		serverTransport, err := New(serverKey)
		Expect(err).ToNot(HaveOccurred())
		clientTransport, err := New(clientKey)
		Expect(err).ToNot(HaveOccurred())
		invalidateCertChain(clientTransport.identity)

		clientInsecureConn, serverInsecureConn := connect()

Marten Seemann's avatar
Marten Seemann committed
193
		done := make(chan struct{})
Marten Seemann's avatar
Marten Seemann committed
194 195 196 197
		go func() {
			defer GinkgoRecover()
			_, err := serverTransport.SecureInbound(context.Background(), serverInsecureConn)
			Expect(err).To(HaveOccurred())
Marten Seemann's avatar
Marten Seemann committed
198 199 200 201
			Expect(err.Error()).To(Or(
				ContainSubstring("crypto/rsa: verification error"),
				ContainSubstring("ECDSA verification failure"),
			))
Marten Seemann's avatar
Marten Seemann committed
202
			close(done)
Marten Seemann's avatar
Marten Seemann committed
203 204 205 206 207
		}()

		_, err = clientTransport.SecureOutbound(context.Background(), clientInsecureConn, serverID)
		Expect(err).To(HaveOccurred())
		Expect(err.Error()).To(ContainSubstring("tls: bad certificate"))
Marten Seemann's avatar
Marten Seemann committed
208
		Eventually(done).Should(BeClosed())
Marten Seemann's avatar
Marten Seemann committed
209 210 211 212 213 214 215 216 217 218 219
	})

	It("fails if the server presents an invalid cert chain", func() {
		serverTransport, err := New(serverKey)
		Expect(err).ToNot(HaveOccurred())
		invalidateCertChain(serverTransport.identity)
		clientTransport, err := New(clientKey)
		Expect(err).ToNot(HaveOccurred())

		clientInsecureConn, serverInsecureConn := connect()

Marten Seemann's avatar
Marten Seemann committed
220
		done := make(chan struct{})
Marten Seemann's avatar
Marten Seemann committed
221 222 223 224
		go func() {
			defer GinkgoRecover()
			_, err := serverTransport.SecureInbound(context.Background(), serverInsecureConn)
			Expect(err).To(HaveOccurred())
Marten Seemann's avatar
Marten Seemann committed
225 226
			// TLS returns a weird error here: "remote error: tls: unexpected message"
			close(done)
Marten Seemann's avatar
Marten Seemann committed
227 228 229 230
		}()

		_, err = clientTransport.SecureOutbound(context.Background(), clientInsecureConn, serverID)
		Expect(err).To(HaveOccurred())
Marten Seemann's avatar
Marten Seemann committed
231 232 233 234
		Expect(err.Error()).To(Or(
			ContainSubstring("crypto/rsa: verification error"),
			ContainSubstring("ECDSA verification failure"),
		))
Marten Seemann's avatar
Marten Seemann committed
235
		Eventually(done).Should(BeClosed())
Marten Seemann's avatar
Marten Seemann committed
236 237
	})
})