Commit 29f8b4b3 authored by Marten Seemann's avatar Marten Seemann

initial commit

parents
package libp2ptls
import (
"crypto/tls"
cs "github.com/libp2p/go-conn-security"
ic "github.com/libp2p/go-libp2p-crypto"
peer "github.com/libp2p/go-libp2p-peer"
)
type conn struct {
*tls.Conn
localPeer peer.ID
privKey ic.PrivKey
remotePeer peer.ID
remotePubKey ic.PubKey
}
var _ cs.Conn = &conn{}
func (c *conn) LocalPeer() peer.ID {
return c.localPeer
}
func (c *conn) LocalPrivateKey() ic.PrivKey {
return c.privKey
}
func (c *conn) RemotePeer() peer.ID {
return c.remotePeer
}
func (c *conn) RemotePublicKey() ic.PubKey {
return c.remotePubKey
}
package libp2ptls
import (
"crypto/rand"
"crypto/tls"
"crypto/x509"
"errors"
"math/big"
"time"
"github.com/gogo/protobuf/proto"
ic "github.com/libp2p/go-libp2p-crypto"
pb "github.com/libp2p/go-libp2p-crypto/pb"
peer "github.com/libp2p/go-libp2p-peer"
)
// Identity is used to secure connections
type Identity struct {
*tls.Config
}
// NewIdentity creates a new identity
func NewIdentity(privKey ic.PrivKey) (*Identity, error) {
conf, err := generateConfig(privKey)
if err != nil {
return nil, err
}
return &Identity{conf}, nil
}
// ConfigForPeer creates a new tls.Config that verifies the peers certificate chain.
// It should be used to create a new tls.Config before dialing.
func (i *Identity) ConfigForPeer(remote peer.ID) *tls.Config {
// We need to check the peer ID in the VerifyPeerCertificate callback.
// The tls.Config it is also used for listening, and we might also have concurrent dials.
// Clone it so we can check for the specific peer ID we're dialing here.
conf := i.Config.Clone()
// We're using InsecureSkipVerify, so the verifiedChains parameter will always be empty.
// We need to parse the certificates ourselves from the raw certs.
conf.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
chain := make([]*x509.Certificate, len(rawCerts))
for i := 0; i < len(rawCerts); i++ {
cert, err := x509.ParseCertificate(rawCerts[i])
if err != nil {
return err
}
chain[i] = cert
}
pubKey, err := getRemotePubKey(chain)
if err != nil {
return err
}
if !remote.MatchesPublicKey(pubKey) {
return errors.New("peer IDs don't match")
}
return nil
}
return conf
}
// KeyFromChain takes a chain of x509.Certificates and returns the peer's public key.
func KeyFromChain(chain []*x509.Certificate) (ic.PubKey, error) {
return getRemotePubKey(chain)
}
const certValidityPeriod = 180 * 24 * time.Hour
func generateConfig(privKey ic.PrivKey) (*tls.Config, error) {
key, cert, err := keyToCertificate(privKey)
if err != nil {
return nil, err
}
return &tls.Config{
InsecureSkipVerify: true, // This is not insecure here. We will verify the cert chain ourselves.
ClientAuth: tls.RequireAnyClientCert,
Certificates: []tls.Certificate{{
Certificate: [][]byte{cert.Raw},
PrivateKey: key,
}},
}, nil
}
func getRemotePubKey(chain []*x509.Certificate) (ic.PubKey, error) {
if len(chain) != 1 {
return nil, errors.New("expected one certificates in the chain")
}
pool := x509.NewCertPool()
pool.AddCert(chain[0])
if _, err := chain[0].Verify(x509.VerifyOptions{Roots: pool}); err != nil {
return nil, err
}
remotePubKey, err := x509.MarshalPKIXPublicKey(chain[0].PublicKey)
if err != nil {
return nil, err
}
return ic.UnmarshalRsaPublicKey(remotePubKey)
}
func keyToCertificate(sk ic.PrivKey) (interface{}, *x509.Certificate, error) {
sn, err := rand.Int(rand.Reader, big.NewInt(1<<62))
if err != nil {
return nil, nil, err
}
tmpl := &x509.Certificate{
SerialNumber: sn,
NotBefore: time.Now().Add(-24 * time.Hour),
NotAfter: time.Now().Add(certValidityPeriod),
}
var publicKey, privateKey interface{}
keyBytes, err := sk.Bytes()
if err != nil {
return nil, nil, err
}
pbmes := new(pb.PrivateKey)
if err := proto.Unmarshal(keyBytes, pbmes); err != nil {
return nil, nil, err
}
switch pbmes.GetType() {
case pb.KeyType_RSA:
k, err := x509.ParsePKCS1PrivateKey(pbmes.GetData())
if err != nil {
return nil, nil, err
}
publicKey = &k.PublicKey
privateKey = k
// TODO: add support for ECDSA
default:
return nil, nil, errors.New("unsupported key type for TLS")
}
certDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, publicKey, privateKey)
if err != nil {
return nil, nil, err
}
cert, err := x509.ParseCertificate(certDER)
if err != nil {
return nil, nil, err
}
return privateKey, cert, nil
}
package libp2ptls
import (
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestLibp2pTLS(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "libp2p TLS Suite")
}
package libp2ptls
import (
"context"
"crypto/tls"
"net"
cs "github.com/libp2p/go-conn-security"
ci "github.com/libp2p/go-libp2p-crypto"
peer "github.com/libp2p/go-libp2p-peer"
)
// ID is the protocol ID (used when negotiating with multistream)
const ID = "/tls/1.0.0"
// Transport constructs secure communication sessions for a peer.
type Transport struct {
identity *Identity
localPeer peer.ID
privKey ci.PrivKey
}
// New creates a TLS encrypted transport
func New(key ci.PrivKey) (*Transport, error) {
id, err := peer.IDFromPrivateKey(key)
if err != nil {
return nil, err
}
identity, err := NewIdentity(key)
if err != nil {
return nil, err
}
return &Transport{
identity: identity,
localPeer: id,
privKey: key,
}, nil
}
var _ cs.Transport = &Transport{}
// SecureInbound runs the TLS handshake as a server.
func (t *Transport) SecureInbound(ctx context.Context, insecure net.Conn) (cs.Conn, error) {
serv := tls.Server(insecure, t.identity.Config)
// TODO: use the ctx
// see https://github.com/golang/go/issues/18482
if err := serv.Handshake(); err != nil {
return nil, err
}
return t.setupConn(serv)
}
// SecureOutbound runs the TLS handshake as a client.
func (t *Transport) SecureOutbound(ctx context.Context, insecure net.Conn, p peer.ID) (cs.Conn, error) {
cl := tls.Client(insecure, t.identity.ConfigForPeer(p))
// TODO: use the ctx
// see https://github.com/golang/go/issues/18482
if err := cl.Handshake(); err != nil {
return nil, err
}
return t.setupConn(cl)
}
func (t *Transport) setupConn(tlsConn *tls.Conn) (cs.Conn, error) {
remotePubKey, err := KeyFromChain(tlsConn.ConnectionState().PeerCertificates)
if err != nil {
return nil, err
}
remotePeerID, err := peer.IDFromPublicKey(remotePubKey)
if err != nil {
return nil, err
}
return &conn{
Conn: tlsConn,
localPeer: t.localPeer,
privKey: t.privKey,
remotePeer: remotePeerID,
remotePubKey: remotePubKey,
}, nil
}
package libp2ptls
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"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) {
key, err := rsa.GenerateKey(rand.Reader, 1024)
Expect(err).ToNot(HaveOccurred())
priv, err := ic.UnmarshalRsaPrivateKey(x509.MarshalPKCS1PrivateKey(key))
Expect(err).ToNot(HaveOccurred())
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) {
key, err := rsa.GenerateKey(rand.Reader, 1024)
Expect(err).ToNot(HaveOccurred())
identity.Config.Certificates[0].PrivateKey = key
}
BeforeEach(func() {
serverID, serverKey = createPeer()
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"))
})
It("fails if the peer ID doesn't match", func() {
thirdPartyID, _ := createPeer()
serverTransport, err := New(serverKey)
Expect(err).ToNot(HaveOccurred())
clientTransport, err := New(clientKey)
Expect(err).ToNot(HaveOccurred())
clientInsecureConn, serverInsecureConn := connect()
go func() {
defer GinkgoRecover()
_, err := serverTransport.SecureInbound(context.Background(), serverInsecureConn)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("tls: bad certificate"))
}()
// dial, but expect the wrong peer ID
_, err = clientTransport.SecureOutbound(context.Background(), clientInsecureConn, thirdPartyID)
Expect(err).To(MatchError("peer IDs don't match"))
})
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()
go func() {
defer GinkgoRecover()
_, err := serverTransport.SecureInbound(context.Background(), serverInsecureConn)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("crypto/rsa: verification error"))
}()
_, err = clientTransport.SecureOutbound(context.Background(), clientInsecureConn, serverID)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("tls: bad certificate"))
})
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()
go func() {
defer GinkgoRecover()
_, err := serverTransport.SecureInbound(context.Background(), serverInsecureConn)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("tls: bad certificate"))
}()
_, err = clientTransport.SecureOutbound(context.Background(), clientInsecureConn, serverID)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("crypto/rsa: verification error"))
})
})
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment