Repository: pion/dtls
Pull request #224 (2020-03-12 07:09:07)

Fix hash algorithm selection
Hash algorithm to verify the certificate was hardcoded to be SHA-256.
Fix to select valid hash which is compatible with the given key.
This also fixes Ed25519 signature calculation.


codecov[bot]: # [Codecov](https://codecov.io/gh/pion/dtls/pull/224?src=pr&el=h1) Report
> Merging [#224](https://codecov.io/gh/pion/dtls/pull/224?src=pr&el=desc) into [master](https://codecov.io/gh/pion/dtls/commit/dd7858bef5844f8098f6feafdad494121efbc0f2?src=pr&el=desc) will **decrease** coverage by `0.3%`.
> The diff coverage is `75.49%`.

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

```diff
@@            Coverage Diff             @@
##           master     #224      +/-   ##
==========================================
- Coverage   76.86%   76.55%   -0.31%     
==========================================
  Files          75       76       +1     
  Lines        4041     4095      +54     
==========================================
+ Hits         3106     3135      +29     
- Misses        636      655      +19     
- Partials      299      305       +6
```

| Flag | Coverage Δ | |
|---|---|---|
| #go | `76.54% <75.49%> (-0.36%)` | :arrow_down: |
| #wasm | `71.51% <72.54%> (-0.14%)` | :arrow_down: |

| [Impacted Files](https://codecov.io/gh/pion/dtls/pull/224?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [handshaker.go](https://codecov.io/gh/pion/dtls/pull/224/diff?src=pr&el=tree#diff-aGFuZHNoYWtlci5nbw==) | `75.69% <ø> (ø)` | :arrow_up: |
| [errors.go](https://codecov.io/gh/pion/dtls/pull/224/diff?src=pr&el=tree#diff-ZXJyb3JzLmdv) | `79.41% <ø> (ø)` | :arrow_up: |
| [config.go](https://codecov.io/gh/pion/dtls/pull/224/diff?src=pr&el=tree#diff-Y29uZmlnLmdv) | `100% <ø> (ø)` | :arrow_up: |
| [hash\_algorithm.go](https://codecov.io/gh/pion/dtls/pull/224/diff?src=pr&el=tree#diff-aGFzaF9hbGdvcml0aG0uZ28=) | `56.89% <0%> (-4.22%)` | :arrow_down: |
| [flight3handler.go](https://codecov.io/gh/pion/dtls/pull/224/diff?src=pr&el=tree#diff-ZmxpZ2h0M2hhbmRsZXIuZ28=) | `90.65% <100%> (-0.58%)` | :arrow_down: |
| [flight1handler.go](https://codecov.io/gh/pion/dtls/pull/224/diff?src=pr&el=tree#diff-ZmxpZ2h0MWhhbmRsZXIuZ28=) | `87.69% <100%> (-1.2%)` | :arrow_down: |
| [conn.go](https://codecov.io/gh/pion/dtls/pull/224/diff?src=pr&el=tree#diff-Y29ubi5nbw==) | `83.24% <50%> (-0.24%)` | :arrow_down: |
| [crypto.go](https://codecov.io/gh/pion/dtls/pull/224/diff?src=pr&el=tree#diff-Y3J5cHRvLmdv) | `57.03% <65%> (+0.11%)` | :arrow_up: |
| [flight4handler.go](https://codecov.io/gh/pion/dtls/pull/224/diff?src=pr&el=tree#diff-ZmxpZ2h0NGhhbmRsZXIuZ28=) | `78.8% <73.33%> (-1.58%)` | :arrow_down: |
| [flight5handler.go](https://codecov.io/gh/pion/dtls/pull/224/diff?src=pr&el=tree#diff-ZmxpZ2h0NWhhbmRsZXIuZ28=) | `80.09% <80%> (-0.01%)` | :arrow_down: |
| ... and [3 more](https://codecov.io/gh/pion/dtls/pull/224/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pion/dtls/pull/224?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/224?src=pr&el=footer). Last update [dd7858b...009d8d2](https://codecov.io/gh/pion/dtls/pull/224?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/config.go b/config.go
index 059865f..ad082f6 100644
--- a/config.go
+++ b/config.go
@@ -24,6 +24,9 @@ type Config struct {
 	// If CipherSuites is nil, a default list is used
 	CipherSuites []CipherSuiteID
 
+	// SignatureSchemes contains the signature and hash schemes that the peer requests to verify.
+	SignatureSchemes []tls.SignatureScheme
+
 	// SRTPProtectionProfiles are the supported protection profiles
 	// Clients will send this via use_srtp and assert that the server properly responds
 	// Servers will assert that clients send one of these profiles and will respond as needed
diff --git a/conn.go b/conn.go
index f0a20ef..0516561 100644
--- a/conn.go
+++ b/conn.go
@@ -84,6 +84,11 @@ func createConn(ctx context.Context, nextConn net.Conn, config *Config, isClient
 		return nil, err
 	}
 
+	signatureSchemes, err := parseSignatureSchemes(config.SignatureSchemes)
+	if err != nil {
+		return nil, err
+	}
+
 	workerInterval := initialTickerInterval
 	if config.FlightInterval != 0 {
 		workerInterval = config.FlightInterval
@@ -150,6 +155,7 @@ func createConn(ctx context.Context, nextConn net.Conn, config *Config, isClient
 		localPSKCallback:            config.PSK,
 		localPSKIdentityHint:        config.PSKIdentityHint,
 		localCipherSuites:           cipherSuites,
+		localSignatureSchemes:       signatureSchemes,
 		extendedMasterSecret:        config.ExtendedMasterSecret,
 		localSRTPProtectionProfiles: config.SRTPProtectionProfiles,
 		serverName:                  serverName,
diff --git a/crypto.go b/crypto.go
index 3634a4f..4eb2a99 100644
--- a/crypto.go
+++ b/crypto.go
@@ -19,7 +19,7 @@ type ecdsaSignature struct {
 	R, S *big.Int
 }
 
-func valueKeySignature(clientRandom, serverRandom, publicKey []byte, namedCurve namedCurve, hashAlgorithm hashAlgorithm) []byte {
+func valueKeyMessage(clientRandom, serverRandom, publicKey []byte, namedCurve namedCurve) []byte {
 	serverECDHParams := make([]byte, 4)
 	serverECDHParams[0] = 3 // named curve
 	binary.BigEndian.PutUint16(serverECDHParams[1:], uint16(namedCurve))
@@ -30,7 +30,8 @@ func valueKeySignature(clientRandom, serverRandom, publicKey []byte, namedCurve
 	plaintext = append(plaintext, serverRandom...)
 	plaintext = append(plaintext, serverECDHParams...)
 	plaintext = append(plaintext, publicKey...)
-	return hashAlgorithm.digest(plaintext)
+
+	return plaintext
 }
 
 // If the client provided a "signature_algorithms" extension, then all
@@ -39,21 +40,23 @@ func valueKeySignature(clientRandom, serverRandom, publicKey []byte, namedCurve
 //
 // https://tools.ietf.org/html/rfc5246#section-7.4.2
 func generateKeySignature(clientRandom, serverRandom, publicKey []byte, namedCurve namedCurve, privateKey crypto.PrivateKey, hashAlgorithm hashAlgorithm) ([]byte, error) {
-	hashed := valueKeySignature(clientRandom, serverRandom, publicKey, namedCurve, hashAlgorithm)
+	msg := valueKeyMessage(clientRandom, serverRandom, publicKey, namedCurve)
 	switch p := privateKey.(type) {
 	case ed25519.PrivateKey:
 		// https://crypto.stackexchange.com/a/55483
-		return p.Sign(rand.Reader, hashed, crypto.Hash(0))
+		return p.Sign(rand.Reader, msg, crypto.Hash(0))
 	case *ecdsa.PrivateKey:
-		return p.Sign(rand.Reader, hashed, crypto.SHA256)
+		hashed := hashAlgorithm.digest(msg)
+		return p.Sign(rand.Reader, hashed, hashAlgorithm.cryptoHash())
 	case *rsa.PrivateKey:
-		return p.Sign(rand.Reader, hashed, crypto.SHA256)
+		hashed := hashAlgorithm.digest(msg)
+		return p.Sign(rand.Reader, hashed, hashAlgorithm.cryptoHash())
 	}
 
 	return nil, errKeySignatureGenerateUnimplemented
 }
 
-func verifyKeySignature(hash, remoteKeySignature []byte, hashAlgorithm hashAlgorithm, rawCertificates [][]byte) error {
+func verifyKeySignature(message, remoteKeySignature []byte, hashAlgorithm hashAlgorithm, rawCertificates [][]byte) error {
 	if len(rawCertificates) == 0 {
 		return errLengthMismatch
 	}
@@ -64,7 +67,7 @@ func verifyKeySignature(hash, remoteKeySignature []byte, hashAlgorithm hashAlgor
 
 	switch p := certificate.PublicKey.(type) {
 	case ed25519.PublicKey:
-		if ok := ed25519.Verify(p, hash, remoteKeySignature); !ok {
+		if ok := ed25519.Verify(p, message, remoteKeySignature); !ok {
 			return errKeySignatureMismatch
 		}
 		return nil
@@ -76,14 +79,16 @@ func verifyKeySignature(hash, remoteKeySignature []byte, hashAlgorithm hashAlgor
 		if ecdsaSig.R.Sign() <= 0 || ecdsaSig.S.Sign() <= 0 {
 			return errInvalidECDSASignature
 		}
-		if !ecdsa.Verify(p, hash, ecdsaSig.R, ecdsaSig.S) {
+		hashed := hashAlgorithm.digest(message)
+		if !ecdsa.Verify(p, hashed, ecdsaSig.R, ecdsaSig.S) {
 			return errKeySignatureMismatch
 		}
 		return nil
 	case *rsa.PublicKey:
 		switch certificate.SignatureAlgorithm {
 		case x509.SHA1WithRSA, x509.SHA256WithRSA, x509.SHA384WithRSA, x509.SHA512WithRSA:
-			return rsa.VerifyPKCS1v15(p, hashAlgorithm.cryptoHash(), hash, remoteKeySignature)
+			hashed := hashAlgorithm.digest(message)
+			return rsa.VerifyPKCS1v15(p, hashAlgorithm.cryptoHash(), hashed, remoteKeySignature)
 		}
 	}
 
@@ -98,7 +103,7 @@ func verifyKeySignature(hash, remoteKeySignature []byte, hashAlgorithm hashAlgor
 // CertificateVerify message is sent to explicitly verify possession of
 // the private key in the certificate.
 // https://tools.ietf.org/html/rfc5246#section-7.3
-func generateCertificateVerify(handshakeBodies []byte, privateKey crypto.PrivateKey) ([]byte, error) {
+func generateCertificateVerify(handshakeBodies []byte, privateKey crypto.PrivateKey, hashAlgorithm hashAlgorithm) ([]byte, error) {
 	h := sha256.New()
 	if _, err := h.Write(handshakeBodies); err != nil {
 		return nil, err
@@ -110,9 +115,9 @@ func generateCertificateVerify(handshakeBodies []byte, privateKey crypto.Private
 		// https://crypto.stackexchange.com/a/55483
 		return p.Sign(rand.Reader, hashed, crypto.Hash(0))
 	case *ecdsa.PrivateKey:
-		return p.Sign(rand.Reader, hashed, crypto.SHA256)
+		return p.Sign(rand.Reader, hashed, hashAlgorithm.cryptoHash())
 	case *rsa.PrivateKey:
-		return p.Sign(rand.Reader, hashed, crypto.SHA256)
+		return p.Sign(rand.Reader, hashed, hashAlgorithm.cryptoHash())
 	}
 
 	return nil, errInvalidSignatureAlgorithm
@@ -127,10 +132,9 @@ func verifyCertificateVerify(handshakeBodies []byte, hashAlgorithm hashAlgorithm
 		return err
 	}
 
-	hash := hashAlgorithm.digest(handshakeBodies)
 	switch p := certificate.PublicKey.(type) {
 	case ed25519.PublicKey:
-		if ok := ed25519.Verify(p, hash, remoteKeySignature); !ok {
+		if ok := ed25519.Verify(p, handshakeBodies, remoteKeySignature); !ok {
 			return errKeySignatureMismatch
 		}
 		return nil
@@ -142,6 +146,7 @@ func verifyCertificateVerify(handshakeBodies []byte, hashAlgorithm hashAlgorithm
 		if ecdsaSig.R.Sign() <= 0 || ecdsaSig.S.Sign() <= 0 {
 			return errInvalidECDSASignature
 		}
+		hash := hashAlgorithm.digest(handshakeBodies)
 		if !ecdsa.Verify(p, hash, ecdsaSig.R, ecdsaSig.S) {
 			return errKeySignatureMismatch
 		}
@@ -149,6 +154,7 @@ func verifyCertificateVerify(handshakeBodies []byte, hashAlgorithm hashAlgorithm
 	case *rsa.PublicKey:
 		switch certificate.SignatureAlgorithm {
 		case x509.SHA1WithRSA, x509.SHA256WithRSA, x509.SHA384WithRSA, x509.SHA512WithRSA:
+			hash := hashAlgorithm.digest(handshakeBodies)
 			return rsa.VerifyPKCS1v15(p, hashAlgorithm.cryptoHash(), hash, remoteKeySignature)
 		}
 	}
diff --git a/errors.go b/errors.go
index 43821b3..4546411 100644
--- a/errors.go
+++ b/errors.go
@@ -54,6 +54,7 @@ var (
 	errKeySignatureMismatch             = &ErrFatal{errors.New("expected and actual key signature do not match")}
 	errNilNextConn                      = &ErrFatal{errors.New("Conn can not be created with a nil nextConn")}
 	errNoAvailableCipherSuites          = &ErrFatal{errors.New("connection can not be created, no CipherSuites satisfy this Config")}
+	errNoAvailableSignatureSchemes      = &ErrFatal{errors.New("connection can not be created, no SignatureScheme satisfy this Config")}
 	errNoCertificates                   = &ErrFatal{errors.New("no certificates configured")}
 	errNoConfigProvided                 = &ErrFatal{errors.New("no config provided")}
 	errNoSupportedEllipticCurves        = &ErrFatal{errors.New("client requested zero or more elliptic curves that are not supported by the server")}
diff --git a/flight1handler.go b/flight1handler.go
index f1bed7b..88e9607 100644
--- a/flight1handler.go
+++ b/flight1handler.go
@@ -49,14 +49,7 @@ func flight1Generate(c flightConn, state *State, cache *handshakeCache, cfg *han
 
 	extensions := []extension{
 		&extensionSupportedSignatureAlgorithms{
-			signatureHashAlgorithms: []signatureHashAlgorithm{
-				{hashAlgorithmSHA256, signatureAlgorithmECDSA},
-				{hashAlgorithmSHA384, signatureAlgorithmECDSA},
-				{hashAlgorithmSHA512, signatureAlgorithmECDSA},
-				{hashAlgorithmSHA256, signatureAlgorithmRSA},
-				{hashAlgorithmSHA384, signatureAlgorithmRSA},
-				{hashAlgorithmSHA512, signatureAlgorithmRSA},
-			},
+			signatureHashAlgorithms: cfg.localSignatureSchemes,
 		},
 	}
 	if cfg.localPSKCallback == nil {
diff --git a/flight3handler.go b/flight3handler.go
index 8854727..8949dd9 100644
--- a/flight3handler.go
+++ b/flight3handler.go
@@ -106,14 +106,7 @@ func handleServerKeyExchange(_ flightConn, state *State, cfg *handshakeConfig, h
 func flight3Generate(c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) ([]*packet, *alert, error) {
 	extensions := []extension{
 		&extensionSupportedSignatureAlgorithms{
-			signatureHashAlgorithms: []signatureHashAlgorithm{
-				{hashAlgorithmSHA256, signatureAlgorithmECDSA},
-				{hashAlgorithmSHA384, signatureAlgorithmECDSA},
-				{hashAlgorithmSHA512, signatureAlgorithmECDSA},
-				{hashAlgorithmSHA256, signatureAlgorithmRSA},
-				{hashAlgorithmSHA384, signatureAlgorithmRSA},
-				{hashAlgorithmSHA512, signatureAlgorithmRSA},
-			},
+			signatureHashAlgorithms: cfg.localSignatureSchemes,
 		},
 	}
 	if cfg.localPSKCallback == nil {
diff --git a/flight4handler.go b/flight4handler.go
index 40ba91d..19aa562 100644
--- a/flight4handler.go
+++ b/flight4handler.go
@@ -42,6 +42,18 @@ func flight4Parse(ctx context.Context, c flightConn, state *State, cache *handsh
 			handshakeCachePullRule{handshakeTypeClientKeyExchange, cfg.initialEpoch, true, false},
 		)
 
+		// Verify that the pair of hash algorithm and signiture is listed.
+		var validSignatureScheme bool
+		for _, ss := range cfg.localSignatureSchemes {
+			if ss.hash == h.hashAlgorithm && ss.signature == h.signatureAlgorithm {
+				validSignatureScheme = true
+				break
+			}
+		}
+		if !validSignatureScheme {
+			return 0, &alert{alertLevelFatal, alertInsufficientSecurity}, errNoAvailableSignatureSchemes
+		}
+
 		if err := verifyCertificateVerify(plainText, h.hashAlgorithm, h.signature, state.remoteCertificate); err != nil {
 			return 0, &alert{alertLevelFatal, alertBadCertificate}, err
 		}
@@ -218,7 +230,13 @@ func flight4Generate(c flightConn, state *State, cache *handshakeCache, cfg *han
 			return nil, &alert{alertLevelFatal, alertInternalError}, err
 		}
 
-		signature, err := generateKeySignature(clientRandom, serverRandom, state.localKeypair.publicKey, state.namedCurve, certificate.PrivateKey, hashAlgorithmSHA256)
+		// Find compatible signature scheme
+		signatureHashAlgo, err := selectSignatureScheme(cfg.localSignatureSchemes, certificate.PrivateKey)
+		if err != nil {
+			return nil, &alert{alertLevelFatal, alertInsufficientSecurity}, err
+		}
+
+		signature, err := generateKeySignature(clientRandom, serverRandom, state.localKeypair.publicKey, state.namedCurve, certificate.PrivateKey, signatureHashAlgo.hash)
 		if err != nil {
 			return nil, &alert{alertLevelFatal, alertInternalError}, err
 		}
@@ -234,8 +252,8 @@ func flight4Generate(c flightConn, state *State, cache *handshakeCache, cfg *han
 						ellipticCurveType:  ellipticCurveTypeNamedCurve,
 						namedCurve:         state.namedCurve,
 						publicKey:          state.localKeypair.publicKey,
-						hashAlgorithm:      hashAlgorithmSHA256,
-						signatureAlgorithm: signatureAlgorithmECDSA,
+						hashAlgorithm:      signatureHashAlgo.hash,
+						signatureAlgorithm: signatureHashAlgo.signature,
 						signature:          state.localKeySignature,
 					}},
 			},
@@ -249,15 +267,8 @@ func flight4Generate(c flightConn, state *State, cache *handshakeCache, cfg *han
 					},
 					content: &handshake{
 						handshakeMessage: &handshakeMessageCertificateRequest{
-							certificateTypes: []clientCertificateType{clientCertificateTypeRSASign, clientCertificateTypeECDSASign},
-							signatureHashAlgorithms: []signatureHashAlgorithm{
-								{hashAlgorithmSHA256, signatureAlgorithmRSA},
-								{hashAlgorithmSHA384, signatureAlgorithmRSA},
-								{hashAlgorithmSHA512, signatureAlgorithmRSA},
-								{hashAlgorithmSHA256, signatureAlgorithmECDSA},
-								{hashAlgorithmSHA384, signatureAlgorithmECDSA},
-								{hashAlgorithmSHA512, signatureAlgorithmECDSA},
-							},
+							certificateTypes:        []clientCertificateType{clientCertificateTypeRSASign, clientCertificateTypeECDSASign},
+							signatureHashAlgorithms: cfg.localSignatureSchemes,
 						},
 					},
 				},
diff --git a/flight5handler.go b/flight5handler.go
index 6d1004b..cf286fa 100644
--- a/flight5handler.go
+++ b/flight5handler.go
@@ -155,7 +155,13 @@ func flight5Generate(c flightConn, state *State, cache *handshakeCache, cfg *han
 			handshakeCachePullRule{handshakeTypeClientKeyExchange, cfg.initialEpoch, true, false},
 		), merged...)
 
-		certVerify, err := generateCertificateVerify(plainText, privateKey)
+		// Find compatible signature scheme
+		signatureHashAlgo, err := selectSignatureScheme(cfg.localSignatureSchemes, privateKey)
+		if err != nil {
+			return nil, &alert{alertLevelFatal, alertInsufficientSecurity}, err
+		}
+
+		certVerify, err := generateCertificateVerify(plainText, privateKey, signatureHashAlgo.hash)
 		if err != nil {
 			return nil, &alert{alertLevelFatal, alertInternalError}, err
 		}
@@ -168,8 +174,8 @@ func flight5Generate(c flightConn, state *State, cache *handshakeCache, cfg *han
 				},
 				content: &handshake{
 					handshakeMessage: &handshakeMessageCertificateVerify{
-						hashAlgorithm:      hashAlgorithmSHA256,
-						signatureAlgorithm: signatureAlgorithmECDSA,
+						hashAlgorithm:      signatureHashAlgo.hash,
+						signatureAlgorithm: signatureHashAlgo.signature,
 						signature:          state.localCertificatesVerify,
 					}},
 			},
@@ -272,8 +278,20 @@ func initalizeCipherSuite(state *State, cache *handshakeCache, cfg *handshakeCon
 	}
 
 	if cfg.localPSKCallback == nil {
-		expectedHash := valueKeySignature(clientRandom, serverRandom, h.publicKey, h.namedCurve, h.hashAlgorithm)
-		if err = verifyKeySignature(expectedHash, h.signature, h.hashAlgorithm, state.remoteCertificate); err != nil {
+		// Verify that the pair of hash algorithm and signiture is listed.
+		var validSignatureScheme bool
+		for _, ss := range cfg.localSignatureSchemes {
+			if ss.hash == h.hashAlgorithm && ss.signature == h.signatureAlgorithm {
+				validSignatureScheme = true
+				break
+			}
+		}
+		if !validSignatureScheme {
+			return &alert{alertLevelFatal, alertInsufficientSecurity}, errNoAvailableSignatureSchemes
+		}
+
+		expectedMsg := valueKeyMessage(clientRandom, serverRandom, h.publicKey, h.namedCurve)
+		if err = verifyKeySignature(expectedMsg, h.signature, h.hashAlgorithm, state.remoteCertificate); err != nil {
 			return &alert{alertLevelFatal, alertBadCertificate}, err
 		}
 		var chains [][]*x509.Certificate
diff --git a/handshaker.go b/handshaker.go
index 72e4b3d..a479377 100644
--- a/handshaker.go
+++ b/handshaker.go
@@ -87,7 +87,8 @@ type handshakeFSM struct {
 type handshakeConfig struct {
 	localPSKCallback            PSKCallback
 	localPSKIdentityHint        []byte
-	localCipherSuites           []cipherSuite            // Available CipherSuites, if empty use default list
+	localCipherSuites           []cipherSuite            // Available CipherSuites
+	localSignatureSchemes       []signatureHashAlgorithm // Available signature schemes
 	extendedMasterSecret        ExtendedMasterSecretType // Policy for the Extended Master Support extension
 	localSRTPProtectionProfiles []SRTPProtectionProfile  // Available SRTPProtectionProfiles, if empty no SRTP support
 	serverName                  string
diff --git a/hash_algorithm.go b/hash_algorithm.go
index ef18343..d2f3434 100644
--- a/hash_algorithm.go
+++ b/hash_algorithm.go
@@ -15,12 +15,13 @@ type hashAlgorithm uint16
 // Supported hash hash algorithms
 const (
 	// hashAlgorithmMD2    hashAlgorithm = 0 // Blacklisted
-	hashAlgorithmMD5    hashAlgorithm = 1 // Blacklisted
-	hashAlgorithmSHA1   hashAlgorithm = 2 // Blacklisted
-	hashAlgorithmSHA224 hashAlgorithm = 3
-	hashAlgorithmSHA256 hashAlgorithm = 4
-	hashAlgorithmSHA384 hashAlgorithm = 5
-	hashAlgorithmSHA512 hashAlgorithm = 6
+	hashAlgorithmMD5     hashAlgorithm = 1 // Blacklisted
+	hashAlgorithmSHA1    hashAlgorithm = 2 // Blacklisted
+	hashAlgorithmSHA224  hashAlgorithm = 3
+	hashAlgorithmSHA256  hashAlgorithm = 4
+	hashAlgorithmSHA384  hashAlgorithm = 5
+	hashAlgorithmSHA512  hashAlgorithm = 6
+	hashAlgorithmEd25519 hashAlgorithm = 8
 )
 
 // String makes hashAlgorithm printable
@@ -38,6 +39,8 @@ func (h hashAlgorithm) String() string {
 		return "sha-384" // [RFC4055]
 	case hashAlgorithmSHA512:
 		return "sha-512" // [RFC4055]
+	case hashAlgorithmEd25519:
+		return "null"
 	default:
 		return "unknown hash algorithm"
 	}
@@ -82,16 +85,19 @@ func (h hashAlgorithm) cryptoHash() crypto.Hash {
 		return crypto.SHA384
 	case hashAlgorithmSHA512:
 		return crypto.SHA512
+	case hashAlgorithmEd25519:
+		return crypto.Hash(0)
 	default:
 		return 0
 	}
 }
 
 var hashAlgorithms = map[hashAlgorithm]struct{}{
-	hashAlgorithmMD5:    {},
-	hashAlgorithmSHA1:   {},
-	hashAlgorithmSHA224: {},
-	hashAlgorithmSHA256: {},
-	hashAlgorithmSHA384: {},
-	hashAlgorithmSHA512: {},
+	hashAlgorithmMD5:     {},
+	hashAlgorithmSHA1:    {},
+	hashAlgorithmSHA224:  {},
+	hashAlgorithmSHA256:  {},
+	hashAlgorithmSHA384:  {},
+	hashAlgorithmSHA512:  {},
+	hashAlgorithmEd25519: {},
 }
diff --git a/signature_algorithm.go b/signature_algorithm.go
index c1a8508..f215cfc 100644
--- a/signature_algorithm.go
+++ b/signature_algorithm.go
@@ -4,11 +4,13 @@ package dtls
 type signatureAlgorithm uint16
 
 const (
-	signatureAlgorithmRSA   signatureAlgorithm = 1
-	signatureAlgorithmECDSA signatureAlgorithm = 3
+	signatureAlgorithmRSA     signatureAlgorithm = 1
+	signatureAlgorithmECDSA   signatureAlgorithm = 3
+	signatureAlgorithmEd25519 signatureAlgorithm = 7
 )
 
 var signatureAlgorithms = map[signatureAlgorithm]bool{
-	signatureAlgorithmRSA:   true,
-	signatureAlgorithmECDSA: true,
+	signatureAlgorithmRSA:     true,
+	signatureAlgorithmECDSA:   true,
+	signatureAlgorithmEd25519: true,
 }
diff --git a/signature_hash_algorithm.go b/signature_hash_algorithm.go
index 3f0f9e7..58ac63b 100644
--- a/signature_hash_algorithm.go
+++ b/signature_hash_algorithm.go
@@ -1,6 +1,80 @@
 package dtls
 
+import (
+	"crypto"
+	"crypto/ecdsa"
+	"crypto/rsa"
+	"crypto/tls"
+
+	"golang.org/x/crypto/ed25519"
+	"golang.org/x/xerrors"
+)
+
 type signatureHashAlgorithm struct {
 	hash      hashAlgorithm
 	signature signatureAlgorithm
 }
+
+func defaultSignatureSchemes() []signatureHashAlgorithm {
+	return []signatureHashAlgorithm{
+		{hashAlgorithmSHA256, signatureAlgorithmECDSA},
+		{hashAlgorithmSHA384, signatureAlgorithmECDSA},
+		{hashAlgorithmSHA512, signatureAlgorithmECDSA},
+		{hashAlgorithmSHA256, signatureAlgorithmRSA},
+		{hashAlgorithmSHA384, signatureAlgorithmRSA},
+		{hashAlgorithmSHA512, signatureAlgorithmRSA},
+		{hashAlgorithmEd25519, signatureAlgorithmEd25519},
+	}
+}
+
+// select Signature Scheme returns most preferred and compatible scheme.
+func selectSignatureScheme(sigs []signatureHashAlgorithm, privateKey crypto.PrivateKey) (signatureHashAlgorithm, error) {
+	for _, ss := range sigs {
+		if ss.isCompatible(privateKey) {
+			return ss, nil
+		}
+	}
+	return signatureHashAlgorithm{}, errNoAvailableSignatureSchemes
+}
+
+// isCompatible checks that given private key is compatible with the signature scheme.
+func (s *signatureHashAlgorithm) isCompatible(privateKey crypto.PrivateKey) bool {
+	switch privateKey.(type) {
+	case ed25519.PrivateKey:
+		return s.signature == signatureAlgorithmEd25519
+	case *ecdsa.PrivateKey:
+		return s.signature == signatureAlgorithmECDSA
+	case *rsa.PrivateKey:
+		return s.signature == signatureAlgorithmRSA
+	default:
+		return false
+	}
+}
+
+// parseSignatureSchemes translates []tls.SignatureScheme to []signatureHashAlgorithm.
+// It returns default signature scheme list if no SignatureScheme is passed.
+func parseSignatureSchemes(sigs []tls.SignatureScheme) ([]signatureHashAlgorithm, error) {
+	if len(sigs) == 0 {
+		return defaultSignatureSchemes(), nil
+	}
+	out := []signatureHashAlgorithm{}
+	for _, ss := range sigs {
+		sig := signatureAlgorithm(ss & 0xFF)
+		if _, ok := signatureAlgorithms[sig]; !ok {
+			return nil, &ErrFatal{
+				xerrors.Errorf("SignatureScheme %04x: %w", ss, errInvalidSignatureAlgorithm),
+			}
+		}
+		h := hashAlgorithm(ss >> 8)
+		if _, ok := hashAlgorithms[h]; !ok {
+			return nil, &ErrFatal{
+				xerrors.Errorf("SignatureScheme %04x: %w", ss, errInvalidHashAlgorithm),
+			}
+		}
+		out = append(out, signatureHashAlgorithm{
+			hash:      h,
+			signature: sig,
+		})
+	}
+	return out, nil
+}


Test patch:
diff --git a/conn_test.go b/conn_test.go
index 4f99b20..0922f53 100644
--- a/conn_test.go
+++ b/conn_test.go
@@ -206,8 +206,6 @@ func testServer(ctx context.Context, c net.Conn, cfg *Config, generateCertificat
 }
 
 func TestHandshakeWithAlert(t *testing.T) {
-	alertErr := errors.New("alert: Alert LevelFatal: InsufficientSecurity")
-
 	// Limit runtime in case of deadlocks
 	lim := test.TimeOut(time.Second * 20)
 	defer lim.Stop()
@@ -219,28 +217,59 @@ func TestHandshakeWithAlert(t *testing.T) {
 	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 	defer cancel()
 
-	clientErr := make(chan error, 1)
-
-	ca, cb := dpipe.Pipe()
-	go func() {
-		conf := &Config{
-			CipherSuites: []CipherSuiteID{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
-		}
+	cases := map[string]struct {
+		configServer, configClient *Config
+		errServer, errClient       interface{}
+	}{
+		"CipherSuiteNoIntersection": {
+			configServer: &Config{
+				CipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+			},
+			configClient: &Config{
+				CipherSuites: []CipherSuiteID{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+			},
+			errServer: errCipherSuiteNoIntersection,
+			errClient: "alert: Alert LevelFatal: InsufficientSecurity",
+		},
+		"SignatureSchemesNoIntersection": {
+			configServer: &Config{
+				CipherSuites:     []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+				SignatureSchemes: []tls.SignatureScheme{tls.ECDSAWithP256AndSHA256},
+			},
+			configClient: &Config{
+				CipherSuites:     []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+				SignatureSchemes: []tls.SignatureScheme{tls.ECDSAWithP521AndSHA512},
+			},
+			errServer: "alert: Alert LevelFatal: InsufficientSecurity",
+			errClient: errNoAvailableSignatureSchemes,
+		},
+	}
 
-		_, err := testClient(ctx, ca, conf, true)
-		clientErr <- err
-	}()
+	for name, testCase := range cases {
+		testCase := testCase
+		t.Run(name, func(t *testing.T) {
+			clientErr := make(chan error, 1)
 
-	config := &Config{
-		CipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
-	}
+			ca, cb := dpipe.Pipe()
+			go func() {
+				_, err := testClient(ctx, ca, testCase.configClient, true)
+				clientErr <- err
+			}()
 
-	if _, err := testServer(ctx, cb, config, true); err != errCipherSuiteNoIntersection {
-		t.Fatalf("TestHandshakeWithAlert: Client error exp(%v) failed(%v)", errCipherSuiteNoIntersection, err)
-	}
+			_, errServer := testServer(ctx, cb, testCase.configServer, true)
+			if errExp, ok := testCase.errServer.(error); ok && errServer != errExp {
+				t.Fatalf("Server error exp(%v) failed(%v)", errExp, errServer)
+			} else if strExp, ok := testCase.errServer.(string); ok && errServer.Error() != strExp {
+				t.Fatalf("Server error exp(%s) failed(%v)", strExp, errServer)
+			}
 
-	if err := <-clientErr; err.Error() != alertErr.Error() {
-		t.Fatalf("TestHandshakeWithAlert: Client error exp(%v) failed(%v)", alertErr, err)
+			errClient := <-clientErr
+			if errExp, ok := testCase.errClient.(error); ok && errClient != errExp {
+				t.Fatalf("Client error exp(%v) failed(%v)", errExp, errClient)
+			} else if strExp, ok := testCase.errClient.(string); ok && errClient.Error() != strExp {
+				t.Fatalf("Client error exp(%s) failed(%v)", strExp, errClient)
+			}
+		})
 	}
 }
 
diff --git a/e2e/e2e_openssl_v113_test.go b/e2e/e2e_openssl_v113_test.go
index 979b9e3..1d947b6 100644
--- a/e2e/e2e_openssl_v113_test.go
+++ b/e2e/e2e_openssl_v113_test.go
@@ -7,7 +7,7 @@ import (
 )
 
 func TestPionOpenSSLE2ESimpleED25519(t *testing.T) {
-	t.Skip("TODO: make ED25519 test work with openssl")
+	t.Skip("TODO: waiting OpenSSL's DTLS Ed25519 support")
 	t.Run("OpenSSLServer", func(t *testing.T) {
 		testPionE2ESimpleED25519(t, serverOpenSSL, clientPion)
 	})
diff --git a/handshaker_test.go b/handshaker_test.go
index 138b9fc..45eb9ae 100644
--- a/handshaker_test.go
+++ b/handshaker_test.go
@@ -45,10 +45,11 @@ func TestHandshaker(t *testing.T) {
 	go func() {
 		defer wg.Done()
 		cfg := &handshakeConfig{
-			localCipherSuites:  cipherSuites,
-			localCertificates:  []tls.Certificate{clientCert},
-			insecureSkipVerify: true,
-			log:                logger,
+			localCipherSuites:     cipherSuites,
+			localCertificates:     []tls.Certificate{clientCert},
+			localSignatureSchemes: defaultSignatureSchemes(),
+			insecureSkipVerify:    true,
+			log:                   logger,
 			onFlightState: func(f flightVal, s handshakeState) {
 				if s == handshakeFinished {
 					cancelCli()
@@ -70,10 +71,11 @@ func TestHandshaker(t *testing.T) {
 	go func() {
 		defer wg.Done()
 		cfg := &handshakeConfig{
-			localCipherSuites:  cipherSuites,
-			localCertificates:  []tls.Certificate{clientCert},
-			insecureSkipVerify: true,
-			log:                logger,
+			localCipherSuites:     cipherSuites,
+			localCertificates:     []tls.Certificate{clientCert},
+			localSignatureSchemes: defaultSignatureSchemes(),
+			insecureSkipVerify:    true,
+			log:                   logger,
 			onFlightState: func(f flightVal, s handshakeState) {
 				if s == handshakeFinished {
 					cancelSrv()
diff --git a/hash_algorithm_test.go b/hash_algorithm_test.go
index d6c3603..bf01bd2 100644
--- a/hash_algorithm_test.go
+++ b/hash_algorithm_test.go
@@ -8,6 +8,10 @@ import (
 
 func TestHashAlgorithm_StringRoundtrip(t *testing.T) {
 	for algo := range hashAlgorithms {
+		if algo == hashAlgorithmEd25519 {
+			// Ed25519 does hashing and signing in one time.
+			continue
+		}
 		str := algo.String()
 		hash1 := algo.cryptoHash()
 		hash2, err := fingerprint.HashFromString(str)
diff --git a/signature_hash_algorithm_go113_test.go b/signature_hash_algorithm_go113_test.go
new file mode 100644
index 0000000..2071500
--- /dev/null
+++ b/signature_hash_algorithm_go113_test.go
@@ -0,0 +1,42 @@
+// +build go1.13
+
+package dtls
+
+import (
+	"crypto/tls"
+	"reflect"
+	"testing"
+
+	"golang.org/x/xerrors"
+)
+
+func TestParseSignatureSchemes_Ed25519(t *testing.T) {
+	cases := map[string]struct {
+		input    []tls.SignatureScheme
+		expected []signatureHashAlgorithm
+		err      error
+	}{
+		"Translate": {
+			input: []tls.SignatureScheme{
+				tls.Ed25519,
+			},
+			expected: []signatureHashAlgorithm{
+				{hashAlgorithmEd25519, signatureAlgorithmEd25519},
+			},
+			err: nil,
+		},
+	}
+
+	for name, testCase := range cases {
+		testCase := testCase
+		t.Run(name, func(t *testing.T) {
+			output, err := parseSignatureSchemes(testCase.input)
+			if testCase.err != nil && !xerrors.Is(err, testCase.err) {
+				t.Fatalf("Expected error: %v, got: %v", testCase.err, err)
+			}
+			if !reflect.DeepEqual(testCase.expected, output) {
+				t.Errorf("Expected signatureHashAlgorithm:\n%+v\ngot:\n%+v", testCase.expected, output)
+			}
+		})
+	}
+}
diff --git a/signature_hash_algorithm_test.go b/signature_hash_algorithm_test.go
new file mode 100644
index 0000000..429363d
--- /dev/null
+++ b/signature_hash_algorithm_test.go
@@ -0,0 +1,66 @@
+package dtls
+
+import (
+	"crypto/tls"
+	"reflect"
+	"testing"
+
+	"golang.org/x/xerrors"
+)
+
+func TestParseSignatureSchemes(t *testing.T) {
+	cases := map[string]struct {
+		input    []tls.SignatureScheme
+		expected []signatureHashAlgorithm
+		err      error
+	}{
+		"Translate": {
+			input: []tls.SignatureScheme{
+				tls.ECDSAWithP256AndSHA256,
+				tls.ECDSAWithP384AndSHA384,
+				tls.ECDSAWithP521AndSHA512,
+				tls.PKCS1WithSHA256,
+				tls.PKCS1WithSHA384,
+				tls.PKCS1WithSHA512,
+			},
+			expected: []signatureHashAlgorithm{
+				{hashAlgorithmSHA256, signatureAlgorithmECDSA},
+				{hashAlgorithmSHA384, signatureAlgorithmECDSA},
+				{hashAlgorithmSHA512, signatureAlgorithmECDSA},
+				{hashAlgorithmSHA256, signatureAlgorithmRSA},
+				{hashAlgorithmSHA384, signatureAlgorithmRSA},
+				{hashAlgorithmSHA512, signatureAlgorithmRSA},
+			},
+			err: nil,
+		},
+		"InvalidSignatureAlgorithm": {
+			input: []tls.SignatureScheme{
+				tls.ECDSAWithP256AndSHA256, // Valid
+				0x04FF,                     // Invalid: unknown signature with SHA-256
+			},
+			expected: nil,
+			err:      errInvalidSignatureAlgorithm,
+		},
+		"InvalidHashAlgorithm": {
+			input: []tls.SignatureScheme{
+				tls.ECDSAWithP256AndSHA256, // Valid
+				0x0003,                     // Invalid: ECDSA with MD2
+			},
+			expected: nil,
+			err:      errInvalidHashAlgorithm,
+		},
+	}
+
+	for name, testCase := range cases {
+		testCase := testCase
+		t.Run(name, func(t *testing.T) {
+			output, err := parseSignatureSchemes(testCase.input)
+			if testCase.err != nil && !xerrors.Is(err, testCase.err) {
+				t.Fatalf("Expected error: %v, got: %v", testCase.err, err)
+			}
+			if !reflect.DeepEqual(testCase.expected, output) {
+				t.Errorf("Expected signatureHashAlgorithm:\n%+v\ngot:\n%+v", testCase.expected, output)
+			}
+		})
+	}
+}