transport_test.go 7.53 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
		var priv ic.PrivKey
		if mrand.Int()%2 == 0 {
Marten Seemann's avatar
Marten Seemann committed
29
			fmt.Fprintf(GinkgoWriter, " using an ECDSA key: ")
Marten Seemann's avatar
Marten Seemann committed
30 31 32 33
			var err error
			priv, _, err = ic.GenerateECDSAKeyPair(rand.Reader)
			Expect(err).ToNot(HaveOccurred())
		} else {
Marten Seemann's avatar
Marten Seemann committed
34
			fmt.Fprintf(GinkgoWriter, " using an RSA key: ")
Marten Seemann's avatar
Marten Seemann committed
35 36 37 38
			var err error
			priv, _, err = ic.GenerateRSAKeyPair(1024, rand.Reader)
			Expect(err).ToNot(HaveOccurred())
		}
Marten Seemann's avatar
Marten Seemann committed
39 40
		id, err := peer.IDFromPrivateKey(priv)
		Expect(err).ToNot(HaveOccurred())
Marten Seemann's avatar
Marten Seemann committed
41
		fmt.Fprintln(GinkgoWriter, id.Pretty())
Marten Seemann's avatar
Marten Seemann committed
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
		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
63 64 65 66 67 68 69 70 71 72 73 74
		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
75 76 77
	}

	BeforeEach(func() {
Marten Seemann's avatar
Marten Seemann committed
78
		fmt.Fprintf(GinkgoWriter, "Initializing a server")
Marten Seemann's avatar
Marten Seemann committed
79
		serverID, serverKey = createPeer()
Marten Seemann's avatar
Marten Seemann committed
80
		fmt.Fprintf(GinkgoWriter, "Initializing a client")
Marten Seemann's avatar
Marten Seemann committed
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 121
		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"))
	})

122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
	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)
138
		Expect(err).To(MatchError(context.Canceled))
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
	})

	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)
154
			Expect(err).To(MatchError(context.Canceled))
155 156 157 158 159
		}()
		_, err = clientTransport.SecureOutbound(context.Background(), clientInsecureConn, serverID)
		Expect(err).To(HaveOccurred())
	})

Marten Seemann's avatar
Marten Seemann committed
160
	It("fails if the peer ID doesn't match", func() {
Marten Seemann's avatar
Marten Seemann committed
161
		fmt.Fprintf(GinkgoWriter, "Creating another peer")
Marten Seemann's avatar
Marten Seemann committed
162 163 164 165 166 167 168 169 170
		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
171
		done := make(chan struct{})
Marten Seemann's avatar
Marten Seemann committed
172 173 174 175 176
		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
177
			close(done)
Marten Seemann's avatar
Marten Seemann committed
178 179 180 181
		}()
		// 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
182
		Eventually(done).Should(BeClosed())
Marten Seemann's avatar
Marten Seemann committed
183 184 185 186 187 188 189 190 191 192 193
	})

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

		_, 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
209
		Eventually(done).Should(BeClosed())
Marten Seemann's avatar
Marten Seemann committed
210 211 212 213 214 215 216 217 218 219 220
	})

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

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