Repository: pion/dtls
Pull request #153 (2019-11-23 08:52:19)

similar Config as tls.Config
Certificate -> Certificates
ClientCAs for client

Sean-Der: This is all @jkralik's work, I accidentally broke his PR when pushing to a branch on his repo. I am adding him to the Pion organization so we can continue to work together.

The only changes I made were fixing merge conflicts/updating the samples
codecov-io: # [Codecov](https://codecov.io/gh/pion/dtls/pull/153?src=pr&el=h1) Report
> Merging [#153](https://codecov.io/gh/pion/dtls/pull/153?src=pr&el=desc) into [master](https://codecov.io/gh/pion/dtls/commit/624f55f1bfa0a5bae5c8549911190db13e77e53f?src=pr&el=desc) will **increase** coverage by `0.02%`.
> The diff coverage is `84.09%`.

[![Impacted file tree graph](https://codecov.io/gh/pion/dtls/pull/153/graphs/tree.svg?width=650&token=bcCB2jB88I&height=150&src=pr)](https://codecov.io/gh/pion/dtls/pull/153?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #153      +/-   ##
==========================================
+ Coverage   73.27%   73.29%   +0.02%     
==========================================
  Files          54       54              
  Lines        3285     3295      +10     
==========================================
+ Hits         2407     2415       +8     
- Misses        609      611       +2     
  Partials      269      269
```


| [Impacted Files](https://codecov.io/gh/pion/dtls/pull/153?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [config.go](https://codecov.io/gh/pion/dtls/pull/153/diff?src=pr&el=tree#diff-Y29uZmlnLmdv) | `100% <100%> (ø)` | :arrow_up: |
| [conn.go](https://codecov.io/gh/pion/dtls/pull/153/diff?src=pr&el=tree#diff-Y29ubi5nbw==) | `80.04% <100%> (+0.52%)` | :arrow_up: |
| [crypto.go](https://codecov.io/gh/pion/dtls/pull/153/diff?src=pr&el=tree#diff-Y3J5cHRvLmdv) | `56.92% <66.66%> (-1.29%)` | :arrow_down: |
| [client\_handlers.go](https://codecov.io/gh/pion/dtls/pull/153/diff?src=pr&el=tree#diff-Y2xpZW50X2hhbmRsZXJzLmdv) | `82.32% <78.57%> (-0.65%)` | :arrow_down: |
| [server\_handlers.go](https://codecov.io/gh/pion/dtls/pull/153/diff?src=pr&el=tree#diff-c2VydmVyX2hhbmRsZXJzLmdv) | `85.36% <81.81%> (-0.04%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pion/dtls/pull/153?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pion/dtls/pull/153?src=pr&el=footer). Last update [624f55f...5594c02](https://codecov.io/gh/pion/dtls/pull/153?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

Sean-Der: Fantastic work @jkralik thanks for continuing to send this stuff to us, really appreciate `go-coap` using it :)
Sean-Der: This LGTM! Thank you so much for taking this on @jkralik.

I can't approve since I opened the CR, but would be awesome if others can take a look when they get a chance :) no rush
Sean-Der: @jkralik if you are happy with it merge away :)

I will be updating `pion/webrtc` right away, and will report back if I hit anything.

Patch:
diff --git a/client_handlers.go b/client_handlers.go
index b702aef..5029f57 100644
--- a/client_handlers.go
+++ b/client_handlers.go
@@ -2,6 +2,7 @@ package dtls
 
 import (
 	"bytes"
+	"crypto/x509"
 	"fmt"
 	"sync/atomic"
 )
@@ -34,22 +35,23 @@ func initalizeCipherSuite(c *Conn, h *handshakeMessageServerKeyExchange) (*alert
 		}
 	}
 
-	if err := c.state.cipherSuite.init(c.state.masterSecret, clientRandom, serverRandom /* isClient */, true); err != nil {
+	if err = c.state.cipherSuite.init(c.state.masterSecret, clientRandom, serverRandom /* isClient */, true); err != nil {
 		return &alert{alertLevelFatal, alertInternalError}, err
 	}
 
 	if c.localPSKCallback == nil {
 		expectedHash := valueKeySignature(clientRandom, serverRandom, h.publicKey, h.namedCurve, h.hashAlgorithm)
-		if err := verifyKeySignature(expectedHash, h.signature, h.hashAlgorithm, c.state.remoteCertificate); err != nil {
+		if err = verifyKeySignature(expectedHash, h.signature, h.hashAlgorithm, c.state.remoteCertificate); err != nil {
 			return &alert{alertLevelFatal, alertBadCertificate}, err
 		}
+		var chains [][]*x509.Certificate
 		if !c.insecureSkipVerify {
-			if err := verifyServerCert(c.state.remoteCertificate, c.rootCAs, c.serverName); err != nil {
+			if chains, err = verifyServerCert(c.state.remoteCertificate, c.rootCAs, c.serverName); err != nil {
 				return &alert{alertLevelFatal, alertBadCertificate}, err
 			}
 		}
 		if c.verifyPeerCertificate != nil {
-			if err := c.verifyPeerCertificate(c.state.remoteCertificate, !c.insecureSkipVerify); err != nil {
+			if err = c.verifyPeerCertificate(c.state.remoteCertificate, chains); err != nil {
 				return &alert{alertLevelFatal, alertBadCertificate}, err
 			}
 		}
@@ -318,6 +320,10 @@ func clientFlightHandler(c *Conn) (bool, *alert, error) {
 
 		messageSequence := c.handshakeMessageSequence
 		if c.remoteRequestedCertificate {
+			var certificate [][]byte
+			if len(c.localCertificates) > 0 {
+				certificate = c.localCertificates[0].Certificate
+			}
 			c.bufferPacket(&packet{
 				record: &recordLayer{
 					recordLayerHeader: recordLayerHeader{
@@ -328,7 +334,7 @@ func clientFlightHandler(c *Conn) (bool, *alert, error) {
 							messageSequence: uint16(messageSequence),
 						},
 						handshakeMessage: &handshakeMessageCertificate{
-							certificate: c.localCertificate.Certificate,
+							certificate: certificate,
 						}},
 				},
 			})
@@ -392,8 +398,8 @@ func clientFlightHandler(c *Conn) (bool, *alert, error) {
 		// If the client has sent a certificate with signing ability, a digitally-signed
 		// CertificateVerify message is sent to explicitly verify possession of the
 		// private key in the certificate.
-		if c.remoteRequestedCertificate && c.localCertificate.PrivateKey != nil {
-			if len(c.localCertificateVerify) == 0 {
+		if c.remoteRequestedCertificate && len(c.localCertificates) > 0 {
+			if len(c.localCertificatesVerify) == 0 {
 				plainText := c.handshakeCache.pullAndMerge(
 					handshakeCachePullRule{handshakeTypeClientHello, true},
 					handshakeCachePullRule{handshakeTypeServerHello, false},
@@ -404,12 +410,12 @@ func clientFlightHandler(c *Conn) (bool, *alert, error) {
 					handshakeCachePullRule{handshakeTypeCertificate, true},
 					handshakeCachePullRule{handshakeTypeClientKeyExchange, true},
 				)
-
-				certVerify, err := generateCertificateVerify(plainText, c.localCertificate.PrivateKey)
+				//TODO: choose right certficate by CAs provided in Certificate Request.
+				certVerify, err := generateCertificateVerify(plainText, c.localCertificates[0].PrivateKey)
 				if err != nil {
 					return false, &alert{alertLevelFatal, alertInternalError}, err
 				}
-				c.localCertificateVerify = certVerify
+				c.localCertificatesVerify = certVerify
 			}
 
 			c.bufferPacket(&packet{
@@ -424,7 +430,7 @@ func clientFlightHandler(c *Conn) (bool, *alert, error) {
 						handshakeMessage: &handshakeMessageCertificateVerify{
 							hashAlgorithm:      HashAlgorithmSHA256,
 							signatureAlgorithm: signatureAlgorithmECDSA,
-							signature:          c.localCertificateVerify,
+							signature:          c.localCertificatesVerify,
 						}},
 				},
 			})
diff --git a/config.go b/config.go
index 6097098..2221f04 100644
--- a/config.go
+++ b/config.go
@@ -17,7 +17,8 @@ type Config struct {
 	// Certificates contains certificate chain to present to the other side of the connection.
 	// Server MUST set this if PSK is non-nil
 	// client SHOULD sets this so CertificateRequests can be handled if PSK is non-nil
-	Certificate tls.Certificate
+	// TODO: add support to use more certificates then one.
+	Certificates []tls.Certificate
 
 	// CipherSuites is a list of supported cipher suites.
 	// If CipherSuites is nil, a default list is used
@@ -63,14 +64,19 @@ type Config struct {
 	// considering this callback. If normal verification is disabled by
 	// setting InsecureSkipVerify, or (for a server) when ClientAuth is
 	// RequestClientCert or RequireAnyClientCert, then this callback will
-	// be considered but the verified flag will always be false.
-	VerifyPeerCertificate func(rawCert [][]byte, verified bool) error
+	// be considered but the verifiedChains will always be nil.
+	VerifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
 
 	// RootCAs defines the set of root certificate authorities
 	// that one peer uses when verifying the other peer's certificates.
 	// If RootCAs is nil, TLS uses the host's root CA set.
 	RootCAs *x509.CertPool
 
+	// ClientCAs defines the set of root certificate authorities
+	// that servers use if required to verify a client certificate
+	// by the policy in ClientAuth.
+	ClientCAs *x509.CertPool
+
 	// ServerName is used to verify the hostname on the returned
 	// certificates unless InsecureSkipVerify is given.
 	ServerName string
@@ -127,18 +133,23 @@ func validateConfig(config *Config) error {
 	switch {
 	case config == nil:
 		return errNoConfigProvided
-	case config.Certificate.PrivateKey != nil && config.PSK != nil:
+	case len(config.Certificates) > 0 && config.PSK != nil:
 		return errPSKAndCertificate
 	case config.PSKIdentityHint != nil && config.PSK == nil:
 		return errIdentityNoPSK
 	}
 
-	if config.Certificate.PrivateKey != nil {
-		switch config.Certificate.PrivateKey.(type) {
-		case ed25519.PrivateKey:
-		case *ecdsa.PrivateKey:
-		default:
-			return errInvalidPrivateKey
+	for _, cert := range config.Certificates {
+		if cert.Certificate == nil {
+			return errInvalidCertificate
+		}
+		if cert.PrivateKey != nil {
+			switch cert.PrivateKey.(type) {
+			case ed25519.PrivateKey:
+			case *ecdsa.PrivateKey:
+			default:
+				return errInvalidPrivateKey
+			}
 		}
 	}
 
diff --git a/conn.go b/conn.go
index 97e33f7..243db3c 100644
--- a/conn.go
+++ b/conn.go
@@ -54,23 +54,24 @@ type Conn struct {
 	clientAuth           ClientAuthType           // If we are a client should we request a client certificate
 	extendedMasterSecret ExtendedMasterSecretType // Policy for the Extended Master Support extension
 
-	currFlight       *flight
-	namedCurve       namedCurve
-	localCertificate tls.Certificate
-	localKeypair     *namedCurveKeypair
-	cookie           []byte
+	currFlight        *flight
+	namedCurve        namedCurve
+	localCertificates []tls.Certificate
+	localKeypair      *namedCurveKeypair
+	cookie            []byte
 
 	localPSKCallback     PSKCallback
 	localPSKIdentityHint []byte
 
-	localCertificateVerify    []byte // cache CertificateVerify
+	localCertificatesVerify   []byte // cache CertificateVerify
 	localVerifyData           []byte // cached VerifyData
 	localKeySignature         []byte // cached keySignature
 	remoteCertificateVerified bool
 
 	insecureSkipVerify    bool
-	verifyPeerCertificate func(rawCert [][]byte, verified bool) error
+	verifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
 	rootCAs               *x509.CertPool
+	clientCAs             *x509.CertPool
 	serverName            string
 
 	handshakeMessageSequence       int
@@ -137,12 +138,13 @@ func createConn(nextConn net.Conn, flightHandler flightHandler, handshakeMessage
 		flightHandler:               flightHandler,
 		connectTimeout:              connectTimeout,
 		maximumTransmissionUnit:     mtu,
-		localCertificate:            config.Certificate,
+		localCertificates:           config.Certificates,
 		clientAuth:                  config.ClientAuth,
 		extendedMasterSecret:        config.ExtendedMasterSecret,
 		insecureSkipVerify:          config.InsecureSkipVerify,
 		verifyPeerCertificate:       config.VerifyPeerCertificate,
 		rootCAs:                     config.RootCAs,
+		clientCAs:                   config.ClientCAs,
 		serverName:                  config.ServerName,
 		localSRTPProtectionProfiles: config.SRTPProtectionProfiles,
 		localCipherSuites:           cipherSuites,
@@ -232,7 +234,7 @@ func Server(conn net.Conn, config *Config) (*Conn, error) {
 	switch {
 	case config == nil:
 		return nil, errNoConfigProvided
-	case config.PSK == nil && (config.Certificate.PrivateKey == nil || config.Certificate.Certificate == nil):
+	case config.PSK == nil && len(config.Certificates) == 0:
 		return nil, errServerMustHaveCertificate
 	}
 
diff --git a/crypto.go b/crypto.go
index 1ce104d..e679152 100644
--- a/crypto.go
+++ b/crypto.go
@@ -172,10 +172,10 @@ func loadCerts(rawCertificates [][]byte) ([]*x509.Certificate, error) {
 	return certs, nil
 }
 
-func verifyClientCert(rawCertificates [][]byte, roots *x509.CertPool) error {
+func verifyClientCert(rawCertificates [][]byte, roots *x509.CertPool) (chains [][]*x509.Certificate, err error) {
 	certificate, err := loadCerts(rawCertificates)
 	if err != nil {
-		return err
+		return nil, err
 	}
 	intermediateCAPool := x509.NewCertPool()
 	for _, cert := range certificate[1:] {
@@ -187,16 +187,13 @@ func verifyClientCert(rawCertificates [][]byte, roots *x509.CertPool) error {
 		Intermediates: intermediateCAPool,
 		KeyUsages:     []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
 	}
-	if _, err := certificate[0].Verify(opts); err != nil {
-		return err
-	}
-	return nil
+	return certificate[0].Verify(opts)
 }
 
-func verifyServerCert(rawCertificates [][]byte, roots *x509.CertPool, serverName string) error {
+func verifyServerCert(rawCertificates [][]byte, roots *x509.CertPool, serverName string) (chains [][]*x509.Certificate, err error) {
 	certificate, err := loadCerts(rawCertificates)
 	if err != nil {
-		return err
+		return nil, err
 	}
 	intermediateCAPool := x509.NewCertPool()
 	for _, cert := range certificate[1:] {
@@ -208,10 +205,7 @@ func verifyServerCert(rawCertificates [][]byte, roots *x509.CertPool, serverName
 		DNSName:       serverName,
 		Intermediates: intermediateCAPool,
 	}
-	if _, err := certificate[0].Verify(opts); err != nil {
-		return err
-	}
-	return nil
+	return certificate[0].Verify(opts)
 }
 
 func generateAEADAdditionalData(h *recordLayerHeader, payloadLen int) []byte {
diff --git a/errors.go b/errors.go
index bddbdfd..0f6703a 100644
--- a/errors.go
+++ b/errors.go
@@ -57,4 +57,5 @@ var (
 	errServerRequiredButNoClientEMS      = errors.New("dtls: Server requires the Extended Master Secret extension, but the client does not support it")
 	errClientRequiredButNoServerEMS      = errors.New("dtls: Client required Extended Master Secret extension, but server does not support it")
 	errInvalidFingerprintLength          = errors.New("dtls: Invalid fingerprint length")
+	errInvalidCertificate                = errors.New("dtls: No certificate provided")
 )
diff --git a/examples/dial/main.go b/examples/dial/main.go
index 80ef7aa..448dd52 100644
--- a/examples/dial/main.go
+++ b/examples/dial/main.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"crypto/tls"
 	"fmt"
 	"net"
 	"time"
@@ -23,7 +24,7 @@ func main() {
 
 	// Prepare the configuration of the DTLS connection
 	config := &dtls.Config{
-		Certificate:          certificate,
+		Certificates:         []tls.Certificate{certificate},
 		InsecureSkipVerify:   true,
 		ExtendedMasterSecret: dtls.RequireExtendedMasterSecret,
 		ConnectTimeout:       dtls.ConnectTimeoutOption(30 * time.Second),
diff --git a/examples/listen/main.go b/examples/listen/main.go
index ef44bd4..36940f7 100644
--- a/examples/listen/main.go
+++ b/examples/listen/main.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"crypto/tls"
 	"fmt"
 	"net"
 	"time"
@@ -23,7 +24,7 @@ func main() {
 
 	// Prepare the configuration of the DTLS connection
 	config := &dtls.Config{
-		Certificate:          certificate,
+		Certificates:         []tls.Certificate{certificate},
 		ExtendedMasterSecret: dtls.RequireExtendedMasterSecret,
 		ConnectTimeout:       dtls.ConnectTimeoutOption(30 * time.Second),
 	}
diff --git a/server_handlers.go b/server_handlers.go
index 3043f72..01abf4a 100644
--- a/server_handlers.go
+++ b/server_handlers.go
@@ -2,6 +2,7 @@ package dtls
 
 import (
 	"bytes"
+	"crypto/x509"
 	"fmt"
 )
 
@@ -83,15 +84,17 @@ func serverHandshakeHandler(c *Conn) (*alert, error) {
 			if err := verifyCertificateVerify(plainText, h.hashAlgorithm, h.signature, c.state.remoteCertificate); err != nil {
 				return &alert{alertLevelFatal, alertBadCertificate}, err
 			}
-			verified := false
-			if !c.insecureSkipVerify && c.clientAuth >= VerifyClientCertIfGiven {
-				if err := verifyClientCert(c.state.remoteCertificate, c.rootCAs); err != nil {
+			var chains [][]*x509.Certificate
+			var err error
+			var verified bool
+			if c.clientAuth >= VerifyClientCertIfGiven {
+				if chains, err = verifyClientCert(c.state.remoteCertificate, c.clientCAs); err != nil {
 					return &alert{alertLevelFatal, alertBadCertificate}, err
 				}
 				verified = true
 			}
 			if c.verifyPeerCertificate != nil {
-				if err := c.verifyPeerCertificate(c.state.remoteCertificate, verified); err != nil {
+				if err := c.verifyPeerCertificate(c.state.remoteCertificate, chains); err != nil {
 					return &alert{alertLevelFatal, alertBadCertificate}, err
 				}
 			}
@@ -339,6 +342,10 @@ func serverFlightHandler(c *Conn) (bool, *alert, error) {
 		messageSequence++
 
 		if c.localPSKCallback == nil {
+			var certificate [][]byte
+			if len(c.localCertificates) > 0 {
+				certificate = c.localCertificates[0].Certificate
+			}
 			c.bufferPacket(&packet{
 				record: &recordLayer{
 					recordLayerHeader: recordLayerHeader{
@@ -349,7 +356,7 @@ func serverFlightHandler(c *Conn) (bool, *alert, error) {
 							messageSequence: uint16(messageSequence),
 						},
 						handshakeMessage: &handshakeMessageCertificate{
-							certificate: c.localCertificate.Certificate,
+							certificate: certificate,
 						}},
 				},
 			})
@@ -365,7 +372,7 @@ func serverFlightHandler(c *Conn) (bool, *alert, error) {
 					return false, &alert{alertLevelFatal, alertInternalError}, err
 				}
 
-				signature, err := generateKeySignature(clientRandom, serverRandom, c.localKeypair.publicKey, c.namedCurve, c.localCertificate.PrivateKey, HashAlgorithmSHA256)
+				signature, err := generateKeySignature(clientRandom, serverRandom, c.localKeypair.publicKey, c.namedCurve, c.localCertificates[0].PrivateKey, HashAlgorithmSHA256)
 				if err != nil {
 					return false, &alert{alertLevelFatal, alertInternalError}, err
 				}


Test patch:
diff --git a/bench_test.go b/bench_test.go
index abb671b..cab9c1c 100644
--- a/bench_test.go
+++ b/bench_test.go
@@ -1,6 +1,7 @@
 package dtls
 
 import (
+	"crypto/tls"
 	"fmt"
 	"net"
 	"testing"
@@ -23,7 +24,7 @@ func TestSimpleReadWrite(t *testing.T) {
 
 	go func() {
 		server, sErr := testServer(cb, &Config{
-			Certificate:   certificate,
+			Certificates:  []tls.Certificate{certificate},
 			LoggerFactory: logging.NewDefaultLoggerFactory(),
 		}, false)
 		if sErr != nil {
@@ -68,7 +69,7 @@ func benchmarkConn(b *testing.B, n int64) {
 		server := make(chan *Conn)
 		go func() {
 			s, sErr := testServer(cb, &Config{
-				Certificate: certificate,
+				Certificates: []tls.Certificate{certificate},
 			}, false)
 			if err != nil {
 				b.Error(sErr)
diff --git a/config_test.go b/config_test.go
index 757c836..c938d5e 100644
--- a/config_test.go
+++ b/config_test.go
@@ -24,7 +24,7 @@ func TestValidateConfig(t *testing.T) {
 		PSK: func(hint []byte) ([]byte, error) {
 			return nil, nil
 		},
-		Certificate: cert,
+		Certificates: []tls.Certificate{cert},
 	}
 	if err = validateConfig(config); err != errPSKAndCertificate {
 		t.Fatalf("TestValidateConfig: Client error exp(%v) failed(%v)", errPSKAndCertificate, err)
@@ -48,12 +48,21 @@ func TestValidateConfig(t *testing.T) {
 	}
 	config = &Config{
 		CipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
-		Certificate:  tls.Certificate{PrivateKey: rsaKey},
+		Certificates: []tls.Certificate{{Certificate: cert.Certificate, PrivateKey: rsaKey}},
 	}
 	if err = validateConfig(config); err != errInvalidPrivateKey {
 		t.Fatalf("TestValidateConfig: Client error exp(%v) failed(%v)", errInvalidPrivateKey, err)
 	}
 
+	// PrivateKey without Certificate
+	config = &Config{
+		CipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+		Certificates: []tls.Certificate{{PrivateKey: cert.PrivateKey}},
+	}
+	if err = validateConfig(config); err != errInvalidCertificate {
+		t.Fatalf("TestValidateConfig: Client error exp(%v) failed(%v)", errInvalidCertificate, err)
+	}
+
 	//Invalid cipher suites
 	config = &Config{CipherSuites: []CipherSuiteID{0x0000}}
 	if err = validateConfig(config); err == nil {
@@ -63,7 +72,7 @@ func TestValidateConfig(t *testing.T) {
 	//Valid config
 	config = &Config{
 		CipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
-		Certificate:  cert,
+		Certificates: []tls.Certificate{cert},
 	}
 	if err = validateConfig(config); err != nil {
 		t.Fatalf("TestValidateConfig: Client error exp(%v) failed(%v)", nil, err)
diff --git a/conn_test.go b/conn_test.go
index 24efc28..83c97e5 100644
--- a/conn_test.go
+++ b/conn_test.go
@@ -3,6 +3,7 @@ package dtls
 import (
 	"bytes"
 	"crypto/rand"
+	"crypto/tls"
 	"crypto/x509"
 	"errors"
 	"fmt"
@@ -110,7 +111,7 @@ func testClient(c net.Conn, cfg *Config, generateCertificate bool) (*Conn, error
 		if err != nil {
 			return nil, err
 		}
-		cfg.Certificate = clientCert
+		cfg.Certificates = []tls.Certificate{clientCert}
 	}
 	cfg.InsecureSkipVerify = true
 	return Client(c, cfg)
@@ -122,7 +123,7 @@ func testServer(c net.Conn, cfg *Config, generateCertificate bool) (*Conn, error
 		if err != nil {
 			return nil, err
 		}
-		cfg.Certificate = serverCert
+		cfg.Certificates = []tls.Certificate{serverCert}
 	}
 	return Server(c, cfg)
 }
@@ -446,78 +447,78 @@ func TestClientCertificate(t *testing.T) {
 		"NoClientCert": {
 			clientCfg: &Config{RootCAs: srvCAPool},
 			serverCfg: &Config{
-				Certificate: srvCert,
-				ClientAuth:  NoClientCert,
-				RootCAs:     caPool,
+				Certificates: []tls.Certificate{srvCert},
+				ClientAuth:   NoClientCert,
+				ClientCAs:    caPool,
 			},
 		},
 		"NoClientCert_cert": {
-			clientCfg: &Config{RootCAs: srvCAPool, Certificate: cert},
+			clientCfg: &Config{RootCAs: srvCAPool, Certificates: []tls.Certificate{cert}},
 			serverCfg: &Config{
-				Certificate: srvCert,
-				ClientAuth:  RequireAnyClientCert,
+				Certificates: []tls.Certificate{srvCert},
+				ClientAuth:   RequireAnyClientCert,
 			},
 		},
 		"RequestClientCert_cert": {
-			clientCfg: &Config{RootCAs: srvCAPool, Certificate: cert},
+			clientCfg: &Config{RootCAs: srvCAPool, Certificates: []tls.Certificate{cert}},
 			serverCfg: &Config{
-				Certificate: srvCert,
-				ClientAuth:  RequestClientCert,
+				Certificates: []tls.Certificate{srvCert},
+				ClientAuth:   RequestClientCert,
 			},
 		},
 		"RequestClientCert_no_cert": {
 			clientCfg: &Config{RootCAs: srvCAPool},
 			serverCfg: &Config{
-				Certificate: srvCert,
-				ClientAuth:  RequestClientCert,
-				RootCAs:     caPool,
+				Certificates: []tls.Certificate{srvCert},
+				ClientAuth:   RequestClientCert,
+				ClientCAs:    caPool,
 			},
 		},
 		"RequireAnyClientCert": {
-			clientCfg: &Config{RootCAs: srvCAPool, Certificate: cert},
+			clientCfg: &Config{RootCAs: srvCAPool, Certificates: []tls.Certificate{cert}},
 			serverCfg: &Config{
-				Certificate: srvCert,
-				ClientAuth:  RequireAnyClientCert,
+				Certificates: []tls.Certificate{srvCert},
+				ClientAuth:   RequireAnyClientCert,
 			},
 		},
 		"RequireAnyClientCert_error": {
 			clientCfg: &Config{RootCAs: srvCAPool},
 			serverCfg: &Config{
-				Certificate: srvCert,
-				ClientAuth:  RequireAnyClientCert,
+				Certificates: []tls.Certificate{srvCert},
+				ClientAuth:   RequireAnyClientCert,
 			},
 			wantErr: true,
 		},
 		"VerifyClientCertIfGiven_no_cert": {
 			clientCfg: &Config{RootCAs: srvCAPool},
 			serverCfg: &Config{
-				Certificate: srvCert,
-				ClientAuth:  VerifyClientCertIfGiven,
-				RootCAs:     caPool,
+				Certificates: []tls.Certificate{srvCert},
+				ClientAuth:   VerifyClientCertIfGiven,
+				ClientCAs:    caPool,
 			},
 		},
 		"VerifyClientCertIfGiven_cert": {
-			clientCfg: &Config{RootCAs: srvCAPool, Certificate: cert},
+			clientCfg: &Config{RootCAs: srvCAPool, Certificates: []tls.Certificate{cert}},
 			serverCfg: &Config{
-				Certificate: srvCert,
-				ClientAuth:  VerifyClientCertIfGiven,
-				RootCAs:     caPool,
+				Certificates: []tls.Certificate{srvCert},
+				ClientAuth:   VerifyClientCertIfGiven,
+				ClientCAs:    caPool,
 			},
 		},
 		"VerifyClientCertIfGiven_error": {
-			clientCfg: &Config{RootCAs: srvCAPool, Certificate: cert},
+			clientCfg: &Config{RootCAs: srvCAPool, Certificates: []tls.Certificate{cert}},
 			serverCfg: &Config{
-				Certificate: srvCert,
-				ClientAuth:  VerifyClientCertIfGiven,
+				Certificates: []tls.Certificate{srvCert},
+				ClientAuth:   VerifyClientCertIfGiven,
 			},
 			wantErr: true,
 		},
 		"RequireAndVerifyClientCert": {
-			clientCfg: &Config{RootCAs: srvCAPool, Certificate: cert},
+			clientCfg: &Config{RootCAs: srvCAPool, Certificates: []tls.Certificate{cert}},
 			serverCfg: &Config{
-				Certificate: srvCert,
-				ClientAuth:  RequireAndVerifyClientCert,
-				RootCAs:     caPool,
+				Certificates: []tls.Certificate{srvCert},
+				ClientAuth:   RequireAndVerifyClientCert,
+				ClientCAs:    caPool,
 			},
 		},
 	}
@@ -559,7 +560,7 @@ func TestClientCertificate(t *testing.T) {
 					t.Errorf("TestClientCertificate: Client did not provide a certificate")
 				}
 
-				if len(actualClientCert) != len(tt.clientCfg.Certificate.Certificate) || !bytes.Equal(tt.clientCfg.Certificate.Certificate[0], actualClientCert[0]) {
+				if len(actualClientCert) != len(tt.clientCfg.Certificates[0].Certificate) || !bytes.Equal(tt.clientCfg.Certificates[0].Certificate[0], actualClientCert[0]) {
 					t.Errorf("TestClientCertificate: Client certificate was not communicated correctly")
 				}
 			}
@@ -574,7 +575,7 @@ func TestClientCertificate(t *testing.T) {
 				t.Errorf("TestClientCertificate: Server did not provide a certificate")
 			}
 
-			if len(actualServerCert) != len(tt.serverCfg.Certificate.Certificate) || !bytes.Equal(tt.serverCfg.Certificate.Certificate[0], actualServerCert[0]) {
+			if len(actualServerCert) != len(tt.serverCfg.Certificates[0].Certificate) || !bytes.Equal(tt.serverCfg.Certificates[0].Certificate[0], actualServerCert[0]) {
 				t.Errorf("TestClientCertificate: Server certificate was not communicated correctly")
 			}
 		})
@@ -735,34 +736,52 @@ func TestServerCertificate(t *testing.T) {
 	}{
 		"no_ca": {
 			clientCfg: &Config{},
-			serverCfg: &Config{Certificate: cert, ClientAuth: NoClientCert},
+			serverCfg: &Config{Certificates: []tls.Certificate{cert}, ClientAuth: NoClientCert},
 			wantErr:   true,
 		},
 		"good_ca": {
 			clientCfg: &Config{RootCAs: caPool},
-			serverCfg: &Config{Certificate: cert, ClientAuth: NoClientCert},
+			serverCfg: &Config{Certificates: []tls.Certificate{cert}, ClientAuth: NoClientCert},
 		},
 		"no_ca_skip_verify": {
 			clientCfg: &Config{InsecureSkipVerify: true},
-			serverCfg: &Config{Certificate: cert, ClientAuth: NoClientCert},
+			serverCfg: &Config{Certificates: []tls.Certificate{cert}, ClientAuth: NoClientCert},
+		},
+		"good_ca_skip_verify_custom_verify_peer": {
+			clientCfg: &Config{RootCAs: caPool, Certificates: []tls.Certificate{cert}},
+			serverCfg: &Config{Certificates: []tls.Certificate{cert}, ClientAuth: RequireAnyClientCert, VerifyPeerCertificate: func(cert [][]byte, chain [][]*x509.Certificate) error {
+				if len(chain) != 0 {
+					return errors.New("not expected chain")
+				}
+				return nil
+			}},
+		},
+		"good_ca_verify_custom_verify_peer": {
+			clientCfg: &Config{RootCAs: caPool, Certificates: []tls.Certificate{cert}},
+			serverCfg: &Config{ClientCAs: caPool, Certificates: []tls.Certificate{cert}, ClientAuth: RequireAndVerifyClientCert, VerifyPeerCertificate: func(cert [][]byte, chain [][]*x509.Certificate) error {
+				if len(chain) == 0 {
+					return errors.New("expected chain")
+				}
+				return nil
+			}},
 		},
 		"good_ca_custom_verify_peer": {
 			clientCfg: &Config{
 				RootCAs: caPool,
-				VerifyPeerCertificate: func([][]byte, bool) error {
+				VerifyPeerCertificate: func([][]byte, [][]*x509.Certificate) error {
 					return errors.New("wrong cert")
 				},
 			},
-			serverCfg: &Config{Certificate: cert, ClientAuth: NoClientCert},
+			serverCfg: &Config{Certificates: []tls.Certificate{cert}, ClientAuth: NoClientCert},
 			wantErr:   true,
 		},
 		"server_name": {
 			clientCfg: &Config{RootCAs: caPool, ServerName: certificate.Subject.CommonName},
-			serverCfg: &Config{Certificate: cert, ClientAuth: NoClientCert},
+			serverCfg: &Config{Certificates: []tls.Certificate{cert}, ClientAuth: NoClientCert},
 		},
 		"server_name_error": {
 			clientCfg: &Config{RootCAs: caPool, ServerName: "barfoo"},
-			serverCfg: &Config{Certificate: cert, ClientAuth: NoClientCert},
+			serverCfg: &Config{Certificates: []tls.Certificate{cert}, ClientAuth: NoClientCert},
 			wantErr:   true,
 		},
 	}
diff --git a/e2e/e2e_lossy_test.go b/e2e/e2e_lossy_test.go
index e2992be..87ecf9e 100644
--- a/e2e/e2e_lossy_test.go
+++ b/e2e/e2e_lossy_test.go
@@ -1,6 +1,7 @@
 package e2e
 
 import (
+	"crypto/tls"
 	"math/rand"
 	"testing"
 	"time"
@@ -118,7 +119,7 @@ func TestPionE2ELossy(t *testing.T) {
 			}
 
 			if test.DoClientAuth {
-				cfg.Certificate = clientCert
+				cfg.Certificates = []tls.Certificate{clientCert}
 			}
 
 			client, startupErr := dtls.Client(br.GetConn0(), cfg)
@@ -127,7 +128,7 @@ func TestPionE2ELossy(t *testing.T) {
 
 		go func() {
 			cfg := &dtls.Config{
-				Certificate:    serverCert,
+				Certificates:   []tls.Certificate{serverCert},
 				FlightInterval: flightInterval,
 				MTU:            test.MTU,
 			}
diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go
index 546b46c..ecdd338 100644
--- a/e2e/e2e_test.go
+++ b/e2e/e2e_test.go
@@ -2,6 +2,7 @@ package e2e
 
 import (
 	"crypto/rand"
+	"crypto/tls"
 	"errors"
 	"io"
 	"net"
@@ -201,7 +202,7 @@ func TestPionE2ESimple(t *testing.T) {
 		}
 
 		cfg := &dtls.Config{
-			Certificate:        cert,
+			Certificates:       []tls.Certificate{cert},
 			CipherSuites:       []dtls.CipherSuiteID{cipherSuite},
 			InsecureSkipVerify: true,
 			ConnectTimeout:     dtls.ConnectTimeoutOption(2 * time.Second),
@@ -234,7 +235,7 @@ func TestPionE2ESimpleED25519(t *testing.T) {
 		}
 
 		cfg := &dtls.Config{
-			Certificate:        cert,
+			Certificates:       []tls.Certificate{cert},
 			CipherSuites:       []dtls.CipherSuiteID{cipherSuite},
 			InsecureSkipVerify: true,
 			ConnectTimeout:     dtls.ConnectTimeoutOption(5 * time.Second),
@@ -288,7 +289,7 @@ func TestPionE2EMTUs(t *testing.T) {
 		}
 
 		cfg := &dtls.Config{
-			Certificate:        cert,
+			Certificates:       []tls.Certificate{cert},
 			CipherSuites:       []dtls.CipherSuiteID{dtls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
 			InsecureSkipVerify: true,
 			MTU:                mtu,
diff --git a/resume_test.go b/resume_test.go
index 94e8a19..e7cb905 100644
--- a/resume_test.go
+++ b/resume_test.go
@@ -2,6 +2,7 @@ package dtls
 
 import (
 	"bytes"
+	"crypto/tls"
 	"fmt"
 	"net"
 	"sync"
@@ -42,7 +43,7 @@ func DoTestResume(t *testing.T, newLocal, newRemote func(net.Conn, *Config) (*Co
 		}
 	}()
 	config := &Config{
-		Certificate:          certificate,
+		Certificates:         []tls.Certificate{certificate},
 		InsecureSkipVerify:   true,
 		ExtendedMasterSecret: RequireExtendedMasterSecret,
 	}