From 44fe87cf2a8621e8840c251ccbe7007d868fb412 Mon Sep 17 00:00:00 2001 From: Jeroen de Bruijn Date: Wed, 25 Mar 2020 11:20:13 +0100 Subject: [PATCH 1/4] feat(cert): add peer certificates to the incoming request This will, for example, allow the server to get the client certificate (chain) in case of DTLS. The certificates can be accessed as `req.PeerCertificates` where `req` is of type `req *coap.Request`. --- net/conn.go | 5 +++++ request.go | 14 +++++++++----- server.go | 12 +++++++++++- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/net/conn.go b/net/conn.go index 7b7158a4..c3eec428 100644 --- a/net/conn.go +++ b/net/conn.go @@ -34,6 +34,11 @@ func (c *Conn) LocalAddr() net.Addr { return c.connection.LocalAddr() } +// Connection returns the network connection. The Conn returned is shared by all invocations of Connection, so do not modify it. +func (c *Conn) Connection() net.Conn { + return c.connection +} + // RemoteAddr returns the remote network address. The Addr returned is shared by all invocations of RemoteAddr, so do not modify it. func (c *Conn) RemoteAddr() net.Addr { return c.connection.RemoteAddr() diff --git a/request.go b/request.go index 2afe0403..1ee83cde 100644 --- a/request.go +++ b/request.go @@ -1,10 +1,14 @@ package coap -import "context" +import ( + "context" + "crypto/x509" +) type Request struct { - Msg Message - Client *ClientConn - Ctx context.Context - Sequence uint64 // discontinuously growing number for every request from connection starts from 0 + Msg Message + PeerCertificates []*x509.Certificate + Client *ClientConn + Ctx context.Context + Sequence uint64 // discontinuously growing number for every request from connection starts from 0 } diff --git a/server.go b/server.go index 5ab0858d..06f6b314 100644 --- a/server.go +++ b/server.go @@ -2,8 +2,10 @@ package coap import ( + "bytes" "context" "crypto/tls" + "crypto/x509" "fmt" "net" "reflect" @@ -549,7 +551,15 @@ func (srv *Server) serveDTLSConnection(ctx *shutdownContext, conn *coapNet.Conn) // We will block poller wait loop when // all pool workers are busy. c := ClientConn{commander: &ClientCommander{session}} - srv.spawnWorker(&Request{Client: &c, Msg: msg, Ctx: sessCtx, Sequence: c.Sequence()}) + + // Try to set the peer certificates, but don't error if it fails as there + // might not be any peer certificates. + dtlsConn := conn.Connection().(*dtls.Conn) + cert := dtlsConn.RemoteCertificate() + flatCerts := bytes.Join(cert, nil) + certs, _ := x509.ParseCertificates(flatCerts) + + srv.spawnWorker(&Request{Client: &c, PeerCertificates: certs, Msg: msg, Ctx: sessCtx, Sequence: c.Sequence()}) } } From a0c2a8ba33daf115e1e1a33c39699ab25ebac849 Mon Sep 17 00:00:00 2001 From: Jeroen de Bruijn Date: Thu, 26 Mar 2020 09:26:57 +0100 Subject: [PATCH 2/4] refactor: move PeerCertificates to the networkSession PeerCertificates are accessed through the ClientCommander and is only implemented for DTLS, the other sessions return `nil`. --- client.go | 6 ++++++ clientcommander.go | 6 ++++++ networksession.go | 4 ++++ request.go | 10 ++++------ server.go | 12 +----------- sessiondtls.go | 23 ++++++++++++++++++++--- sessiontcp.go | 6 ++++++ sessionudp.go | 6 ++++++ 8 files changed, 53 insertions(+), 20 deletions(-) diff --git a/client.go b/client.go index 833379ae..550bac48 100644 --- a/client.go +++ b/client.go @@ -5,6 +5,7 @@ package coap import ( "context" "crypto/tls" + "crypto/x509" "fmt" "io" "net" @@ -322,6 +323,11 @@ func (co *ClientConn) RemoteAddr() net.Addr { return co.commander.RemoteAddr() } +// PeerCertificates implements the networkSession.PeerCertificates method. +func (co *ClientConn) PeerCertificates() []*x509.Certificate { + return co.commander.PeerCertificates() +} + func (co *ClientConn) Exchange(m Message) (Message, error) { return co.commander.ExchangeWithContext(context.Background(), m) } diff --git a/clientcommander.go b/clientcommander.go index d6cc6a9f..233f457e 100644 --- a/clientcommander.go +++ b/clientcommander.go @@ -3,6 +3,7 @@ package coap import ( "bytes" "context" + "crypto/x509" "io" "io/ioutil" "net" @@ -88,6 +89,11 @@ func (cc *ClientCommander) RemoteAddr() net.Addr { return cc.networkSession.RemoteAddr() } +// PeerCertificates implements the networkSession.PeerCertificates method. +func (cc *ClientCommander) PeerCertificates() []*x509.Certificate { + return cc.networkSession.PeerCertificates() +} + // Equal compare two ClientCommanders func (cc *ClientCommander) Equal(cc1 *ClientCommander) bool { return cc.RemoteAddr().String() == cc1.RemoteAddr().String() && cc.LocalAddr().String() == cc1.LocalAddr().String() diff --git a/networksession.go b/networksession.go index 4d36bd4b..212ca29b 100644 --- a/networksession.go +++ b/networksession.go @@ -2,6 +2,7 @@ package coap import ( "context" + "crypto/x509" "net" ) @@ -12,6 +13,9 @@ type networkSession interface { LocalAddr() net.Addr // RemoteAddr returns the net.Addr of the client that sent the current request. RemoteAddr() net.Addr + // PeerCertificates returns the certificate chain presented by the remote + // peer of the current request. + PeerCertificates() []*x509.Certificate // WriteContextMsg writes a reply back to the client. WriteMsgWithContext(ctx context.Context, resp Message) error // Close closes the connection. diff --git a/request.go b/request.go index 1ee83cde..11afb565 100644 --- a/request.go +++ b/request.go @@ -2,13 +2,11 @@ package coap import ( "context" - "crypto/x509" ) type Request struct { - Msg Message - PeerCertificates []*x509.Certificate - Client *ClientConn - Ctx context.Context - Sequence uint64 // discontinuously growing number for every request from connection starts from 0 + Msg Message + Client *ClientConn + Ctx context.Context + Sequence uint64 // discontinuously growing number for every request from connection starts from 0 } diff --git a/server.go b/server.go index 06f6b314..5ab0858d 100644 --- a/server.go +++ b/server.go @@ -2,10 +2,8 @@ package coap import ( - "bytes" "context" "crypto/tls" - "crypto/x509" "fmt" "net" "reflect" @@ -551,15 +549,7 @@ func (srv *Server) serveDTLSConnection(ctx *shutdownContext, conn *coapNet.Conn) // We will block poller wait loop when // all pool workers are busy. c := ClientConn{commander: &ClientCommander{session}} - - // Try to set the peer certificates, but don't error if it fails as there - // might not be any peer certificates. - dtlsConn := conn.Connection().(*dtls.Conn) - cert := dtlsConn.RemoteCertificate() - flatCerts := bytes.Join(cert, nil) - certs, _ := x509.ParseCertificates(flatCerts) - - srv.spawnWorker(&Request{Client: &c, PeerCertificates: certs, Msg: msg, Ctx: sessCtx, Sequence: c.Sequence()}) + srv.spawnWorker(&Request{Client: &c, Msg: msg, Ctx: sessCtx, Sequence: c.Sequence()}) } } diff --git a/sessiondtls.go b/sessiondtls.go index 7ab10c84..3d4bbcae 100644 --- a/sessiondtls.go +++ b/sessiondtls.go @@ -3,16 +3,19 @@ package coap import ( "bytes" "context" + "crypto/x509" "fmt" "net" "github.com/go-ocf/go-coap/codes" coapNet "github.com/go-ocf/go-coap/net" + dtls "github.com/pion/dtls/v2" ) type sessionDTLS struct { *sessionBase - connection *coapNet.Conn + connection *coapNet.Conn + peerCertificates []*x509.Certificate } // newSessionDTLS create new session for DTLS connection @@ -30,9 +33,18 @@ func newSessionDTLS(connection *coapNet.Conn, srv *Server) (networkSession, erro return nil, ErrInvalidBlockWiseSzx } + dtlsConn := connection.Connection().(*dtls.Conn) + cert := dtlsConn.RemoteCertificate() + flatCerts := bytes.Join(cert, nil) + peerCertificates, err := x509.ParseCertificates(flatCerts) + if err != nil { + return nil, err + } + s := sessionDTLS{ - connection: connection, - sessionBase: newBaseSession(BlockWiseTransfer, BlockWiseTransferSzx, srv), + sessionBase: newBaseSession(BlockWiseTransfer, BlockWiseTransferSzx, srv), + connection: connection, + peerCertificates: peerCertificates, } return &s, nil @@ -48,6 +60,11 @@ func (s *sessionDTLS) RemoteAddr() net.Addr { return s.connection.RemoteAddr() } +// PeerCertificates implements the networkSession.PeerCertificates method. +func (s *sessionDTLS) PeerCertificates() []*x509.Certificate { + return s.peerCertificates +} + // BlockWiseTransferEnabled func (s *sessionDTLS) blockWiseEnabled() bool { return s.blockWiseTransfer diff --git a/sessiontcp.go b/sessiontcp.go index 70164ffb..31a83b74 100644 --- a/sessiontcp.go +++ b/sessiontcp.go @@ -3,6 +3,7 @@ package coap import ( "bytes" "context" + "crypto/x509" "fmt" "net" "sync/atomic" @@ -55,6 +56,11 @@ func (s *sessionTCP) RemoteAddr() net.Addr { return s.connection.RemoteAddr() } +// PeerCertificates implements the networkSession.PeerCertificates method. +func (s *sessionTCP) PeerCertificates() []*x509.Certificate { + return nil +} + func (s *sessionTCP) blockWiseEnabled() bool { return s.blockWiseTransfer /*&& atomic.LoadUint32(&s.peerBlockWiseTransfer) != 0*/ } diff --git a/sessionudp.go b/sessionudp.go index f1e49abe..6c73bade 100644 --- a/sessionudp.go +++ b/sessionudp.go @@ -3,6 +3,7 @@ package coap import ( "bytes" "context" + "crypto/x509" "fmt" "net" @@ -57,6 +58,11 @@ func (s *sessionUDP) RemoteAddr() net.Addr { return s.sessionUDPData.RemoteAddr() } +// PeerCertificates implements the networkSession.PeerCertificates method. +func (s *sessionUDP) PeerCertificates() []*x509.Certificate { + return nil +} + // BlockWiseTransferEnabled func (s *sessionUDP) blockWiseEnabled() bool { return s.blockWiseTransfer From 83d4bfbd9542b41991dc35c2ac606d3be93fd04e Mon Sep 17 00:00:00 2001 From: Jeroen de Bruijn Date: Thu, 26 Mar 2020 11:39:23 +0100 Subject: [PATCH 3/4] fix: parse DTLS session certificates only if available --- sessiondtls.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/sessiondtls.go b/sessiondtls.go index 3d4bbcae..f907ae70 100644 --- a/sessiondtls.go +++ b/sessiondtls.go @@ -33,18 +33,21 @@ func newSessionDTLS(connection *coapNet.Conn, srv *Server) (networkSession, erro return nil, ErrInvalidBlockWiseSzx } + s := sessionDTLS{ + sessionBase: newBaseSession(BlockWiseTransfer, BlockWiseTransferSzx, srv), + connection: connection, + } + dtlsConn := connection.Connection().(*dtls.Conn) cert := dtlsConn.RemoteCertificate() - flatCerts := bytes.Join(cert, nil) - peerCertificates, err := x509.ParseCertificates(flatCerts) - if err != nil { - return nil, err - } + if len(cert) > 0 { + flatCerts := bytes.Join(cert, nil) + peerCertificates, err := x509.ParseCertificates(flatCerts) + if err != nil { + return nil, err + } - s := sessionDTLS{ - sessionBase: newBaseSession(BlockWiseTransfer, BlockWiseTransferSzx, srv), - connection: connection, - peerCertificates: peerCertificates, + s.peerCertificates = peerCertificates } return &s, nil From 260b7e6b5965e1d2b7af2a0c055af481b39a0c0d Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Wed, 1 Apr 2020 10:02:31 +0200 Subject: [PATCH 4/4] Add test for PeerCertificates --- dtls_test.go | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/dtls_test.go b/dtls_test.go index ba6f4fe7..fa57f4a0 100644 --- a/dtls_test.go +++ b/dtls_test.go @@ -1,7 +1,10 @@ package coap import ( + "context" + "crypto" "crypto/ecdsa" + "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/tls" @@ -11,9 +14,11 @@ import ( "fmt" "math/big" "os" + "sync" "testing" "time" + coapNet "github.com/go-ocf/go-coap/net" dtls "github.com/pion/dtls/v2" "github.com/stretchr/testify/require" ) @@ -77,8 +82,8 @@ func pemBlockForKey(priv interface{}) *pem.Block { } } -func generateRSACertsPEM(t *testing.T) ([]byte, []byte) { - priv, err := rsa.GenerateKey(rand.Reader, 2048) +func generateCertsPEM(t *testing.T, generateKeyFunc func() (crypto.PrivateKey, error)) ([]byte, []byte) { + priv, err := generateKeyFunc() if err != nil { require.NoError(t, err) } @@ -99,6 +104,18 @@ func generateRSACertsPEM(t *testing.T) ([]byte, []byte) { return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}), pem.EncodeToMemory(pemBlockForKey(priv)) } +func generateRSACertsPEM(t *testing.T) ([]byte, []byte) { + return generateCertsPEM(t, func() (crypto.PrivateKey, error) { + return rsa.GenerateKey(rand.Reader, 2048) + }) +} + +func generateECDSACertsPEM(t *testing.T) ([]byte, []byte) { + return generateCertsPEM(t, func() (crypto.PrivateKey, error) { + return ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + }) +} + func TestRSACerts(t *testing.T) { cert, key := generateRSACertsPEM(t) c, err := tls.X509KeyPair(cert, key) @@ -120,3 +137,50 @@ func TestRSACerts(t *testing.T) { err = s.ListenAndServe() require.Error(t, err) } + +func TestECDSACerts_PeerCertificate(t *testing.T) { + cert, key := generateECDSACertsPEM(t) + c, err := tls.X509KeyPair(cert, key) + require.NoError(t, err) + + config := dtls.Config{ + Certificates: []tls.Certificate{c}, + CipherSuites: []dtls.CipherSuiteID{dtls.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8}, + InsecureSkipVerify: true, + ClientAuth: dtls.RequireAnyClientCert, + VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + return nil + }, + } + l, err := coapNet.NewDTLSListener("udp", ":", &config, time.Millisecond*100) + require.NoError(t, err) + + s := &Server{ + Net: "udp-dtls", + Listener: l, + KeepAlive: MustMakeKeepAlive(time.Second), + } + s.Handler = HandlerFunc(func(w ResponseWriter, r *Request) { + certs := r.Client.PeerCertificates() + require.NotEmpty(t, certs) + err := s.Shutdown() + require.NoError(t, err) + }) + var wg sync.WaitGroup + wg.Add(1) + defer wg.Wait() + go func() { + defer wg.Done() + s.ActivateAndServe() + }() + + conn, err := DialDTLS("udp-dtls", l.Addr().String(), &config) + require.NoError(t, err) + certs := conn.PeerCertificates() + require.NotEmpty(t, certs) + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*100) + defer cancel() + _, err = conn.GetWithContext(ctx, "/") + require.Error(t, err) + conn.Close() +}