Repository: pion/dtls
Pull request #185 (2020-02-03 05:47:19)

Unexpose HashAlgorithm
Move `Fingerprint` under `pkg/crypto/fingerprint/` and
use `crypto.Hash` instead of `HashAlgorithm`.
Unexpose `HashAlgorithm` representation.

### Reference issue
part of #148

codecov-io: # [Codecov](https://codecov.io/gh/pion/dtls/pull/185?src=pr&el=h1) Report
> Merging [#185](https://codecov.io/gh/pion/dtls/pull/185?src=pr&el=desc) into [master](https://codecov.io/gh/pion/dtls/commit/0b80bd9abfa64f92945b81c8acd9407d741a50ed?src=pr&el=desc) will **decrease** coverage by `0.01%`.
> The diff coverage is `48.78%`.

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

```diff
@@            Coverage Diff             @@
##           master     #185      +/-   ##
==========================================
- Coverage    72.6%   72.59%   -0.02%     
==========================================
  Files          60       61       +1     
  Lines        3431     3437       +6     
==========================================
+ Hits         2491     2495       +4     
- Misses        649      650       +1     
- Partials      291      292       +1
```

| Flag | Coverage Δ | |
|---|---|---|
| #go | `72.47% <48.78%> (-0.13%)` | :arrow_down: |
| #wasm | `68.72% <48.78%> (-0.01%)` | :arrow_down: |

| [Impacted Files](https://codecov.io/gh/pion/dtls/pull/185?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pkg/net/fingerprint/hash.go](https://codecov.io/gh/pion/dtls/pull/185/diff?src=pr&el=tree#diff-cGtnL25ldC9maW5nZXJwcmludC9oYXNoLmdv) | `0% <0%> (ø)` | |
| [extension\_supported\_signature\_algorithms.go](https://codecov.io/gh/pion/dtls/pull/185/diff?src=pr&el=tree#diff-ZXh0ZW5zaW9uX3N1cHBvcnRlZF9zaWduYXR1cmVfYWxnb3JpdGhtcy5nbw==) | `40% <0%> (ø)` | :arrow_up: |
| [server\_handlers.go](https://codecov.io/gh/pion/dtls/pull/185/diff?src=pr&el=tree#diff-c2VydmVyX2hhbmRsZXJzLmdv) | `80.52% <100%> (ø)` | :arrow_up: |
| [handshake\_message\_certificate\_request.go](https://codecov.io/gh/pion/dtls/pull/185/diff?src=pr&el=tree#diff-aGFuZHNoYWtlX21lc3NhZ2VfY2VydGlmaWNhdGVfcmVxdWVzdC5nbw==) | `67.44% <100%> (ø)` | :arrow_up: |
| [handshake\_message\_server\_key\_exchange.go](https://codecov.io/gh/pion/dtls/pull/185/diff?src=pr&el=tree#diff-aGFuZHNoYWtlX21lc3NhZ2Vfc2VydmVyX2tleV9leGNoYW5nZS5nbw==) | `57.89% <100%> (ø)` | :arrow_up: |
| [handshake\_message\_certificate\_verify.go](https://codecov.io/gh/pion/dtls/pull/185/diff?src=pr&el=tree#diff-aGFuZHNoYWtlX21lc3NhZ2VfY2VydGlmaWNhdGVfdmVyaWZ5Lmdv) | `65.21% <100%> (ø)` | :arrow_up: |
| [client\_handlers.go](https://codecov.io/gh/pion/dtls/pull/185/diff?src=pr&el=tree#diff-Y2xpZW50X2hhbmRsZXJzLmdv) | `78.7% <100%> (ø)` | :arrow_up: |
| [crypto.go](https://codecov.io/gh/pion/dtls/pull/185/diff?src=pr&el=tree#diff-Y3J5cHRvLmdv) | `56.92% <100%> (ø)` | :arrow_up: |
| [pkg/net/fingerprint/fingerprint.go](https://codecov.io/gh/pion/dtls/pull/185/diff?src=pr&el=tree#diff-cGtnL25ldC9maW5nZXJwcmludC9maW5nZXJwcmludC5nbw==) | `72.72% <72.72%> (ø)` | |
| [hash\_algorithm.go](https://codecov.io/gh/pion/dtls/pull/185/diff?src=pr&el=tree#diff-aGFzaF9hbGdvcml0aG0uZ28=) | `9.25% <9.52%> (+2.11%)` | :arrow_up: |
| ... and [2 more](https://codecov.io/gh/pion/dtls/pull/185/diff?src=pr&el=tree-more) | |

------

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

Sean-Der: :O I love this! 

The more code we hide/delete the better. I really love what you are doing with `pion/dtls`. Sorry I have been distracted, I really want to push Add/Remove (Re-signaling) this week so close :)
Sean-Der: When this lands would you mind tagging + updating `pion/webrtc` as well? I just want to make sure it doesn't have any side effects, and we don't block people not using modules yet.
at-wat: > When this lands would you mind tagging + updating pion/webrtc as well?

Sure, these are used only at a few places; it will be quick.
daenney: I'm a bit confused as to why this moves into `pkg/net`? If I look at the Go stdlib, things in `net` directly deal with network related concerns, whereas this is creating a TLS cert hash.
at-wat: Makes sense. `pkg/crypto` maybe proper location.
at-wat: Fixed sub-package path.
at-wat: Rebased.
I will merge it, after the CI passed.

Patch:
diff --git a/client_handlers.go b/client_handlers.go
index fe78d62..9930cca 100644
--- a/client_handlers.go
+++ b/client_handlers.go
@@ -258,12 +258,12 @@ func clientFlightHandler(c *Conn) (bool, *alert, error) {
 		extensions := []extension{
 			&extensionSupportedSignatureAlgorithms{
 				signatureHashAlgorithms: []signatureHashAlgorithm{
-					{HashAlgorithmSHA256, signatureAlgorithmECDSA},
-					{HashAlgorithmSHA384, signatureAlgorithmECDSA},
-					{HashAlgorithmSHA512, signatureAlgorithmECDSA},
-					{HashAlgorithmSHA256, signatureAlgorithmRSA},
-					{HashAlgorithmSHA384, signatureAlgorithmRSA},
-					{HashAlgorithmSHA512, signatureAlgorithmRSA},
+					{hashAlgorithmSHA256, signatureAlgorithmECDSA},
+					{hashAlgorithmSHA384, signatureAlgorithmECDSA},
+					{hashAlgorithmSHA512, signatureAlgorithmECDSA},
+					{hashAlgorithmSHA256, signatureAlgorithmRSA},
+					{hashAlgorithmSHA384, signatureAlgorithmRSA},
+					{hashAlgorithmSHA512, signatureAlgorithmRSA},
 				},
 			},
 		}
@@ -438,7 +438,7 @@ func clientFlightHandler(c *Conn) (bool, *alert, error) {
 							messageSequence: uint16(messageSequence),
 						},
 						handshakeMessage: &handshakeMessageCertificateVerify{
-							hashAlgorithm:      HashAlgorithmSHA256,
+							hashAlgorithm:      hashAlgorithmSHA256,
 							signatureAlgorithm: signatureAlgorithmECDSA,
 							signature:          c.localCertificatesVerify,
 						}},
diff --git a/crypto.go b/crypto.go
index e679152..3634a4f 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 valueKeySignature(clientRandom, serverRandom, publicKey []byte, namedCurve namedCurve, hashAlgorithm hashAlgorithm) []byte {
 	serverECDHParams := make([]byte, 4)
 	serverECDHParams[0] = 3 // named curve
 	binary.BigEndian.PutUint16(serverECDHParams[1:], uint16(namedCurve))
@@ -38,7 +38,7 @@ func valueKeySignature(clientRandom, serverRandom, publicKey []byte, namedCurve
 // hash/signature algorithm pair that appears in that extension
 //
 // 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) {
+func generateKeySignature(clientRandom, serverRandom, publicKey []byte, namedCurve namedCurve, privateKey crypto.PrivateKey, hashAlgorithm hashAlgorithm) ([]byte, error) {
 	hashed := valueKeySignature(clientRandom, serverRandom, publicKey, namedCurve, hashAlgorithm)
 	switch p := privateKey.(type) {
 	case ed25519.PrivateKey:
@@ -53,7 +53,7 @@ func generateKeySignature(clientRandom, serverRandom, publicKey []byte, namedCur
 	return nil, errKeySignatureGenerateUnimplemented
 }
 
-func verifyKeySignature(hash, remoteKeySignature []byte, hashAlgorithm HashAlgorithm, rawCertificates [][]byte) error {
+func verifyKeySignature(hash, remoteKeySignature []byte, hashAlgorithm hashAlgorithm, rawCertificates [][]byte) error {
 	if len(rawCertificates) == 0 {
 		return errLengthMismatch
 	}
@@ -118,7 +118,7 @@ func generateCertificateVerify(handshakeBodies []byte, privateKey crypto.Private
 	return nil, errInvalidSignatureAlgorithm
 }
 
-func verifyCertificateVerify(handshakeBodies []byte, hashAlgorithm HashAlgorithm, remoteKeySignature []byte, rawCertificates [][]byte) error {
+func verifyCertificateVerify(handshakeBodies []byte, hashAlgorithm hashAlgorithm, remoteKeySignature []byte, rawCertificates [][]byte) error {
 	if len(rawCertificates) == 0 {
 		return errLengthMismatch
 	}
diff --git a/errors.go b/errors.go
index 0f6703a..87a8d3e 100644
--- a/errors.go
+++ b/errors.go
@@ -56,6 +56,5 @@ var (
 	errServerNoMatchingSRTPProfile       = errors.New("dtls: Client requested SRTP but we have no matching profiles")
 	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/extension_supported_signature_algorithms.go b/extension_supported_signature_algorithms.go
index 6deeaa6..0a8a7a0 100644
--- a/extension_supported_signature_algorithms.go
+++ b/extension_supported_signature_algorithms.go
@@ -44,7 +44,7 @@ func (e *extensionSupportedSignatureAlgorithms) Unmarshal(data []byte) error {
 		return errLengthMismatch
 	}
 	for i := 0; i < algorithmCount; i++ {
-		supportedHashAlgorithm := HashAlgorithm(data[extensionSupportedSignatureAlgorithmsHeaderSize+(i*2)])
+		supportedHashAlgorithm := hashAlgorithm(data[extensionSupportedSignatureAlgorithmsHeaderSize+(i*2)])
 		supportedSignatureAlgorithm := signatureAlgorithm(data[extensionSupportedSignatureAlgorithmsHeaderSize+(i*2)+1])
 		if _, ok := hashAlgorithms[supportedHashAlgorithm]; ok {
 			if _, ok := signatureAlgorithms[supportedSignatureAlgorithm]; ok {
diff --git a/fingerprint.go b/fingerprint.go
deleted file mode 100644
index 84c3d8d..0000000
--- a/fingerprint.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package dtls
-
-import (
-	"crypto/x509"
-	"fmt"
-)
-
-// Fingerprint creates a fingerprint for a certificate using the specified hash algorithm
-func Fingerprint(cert *x509.Certificate, algo HashAlgorithm) (string, error) {
-	digest := []byte(fmt.Sprintf("%x", algo.digest(cert.Raw)))
-
-	digestlen := len(digest)
-	if digestlen == 0 {
-		return "", nil
-	}
-	if digestlen%2 != 0 {
-		return "", errInvalidFingerprintLength
-	}
-	res := make([]byte, digestlen>>1+digestlen-1)
-
-	pos := 0
-	for i, c := range digest {
-		res[pos] = c
-		pos++
-		if (i)%2 != 0 && i < digestlen-1 {
-			res[pos] = byte(':')
-			pos++
-		}
-	}
-
-	return string(res), nil
-}
diff --git a/handshake_message_certificate_request.go b/handshake_message_certificate_request.go
index a429daa..eafe174 100644
--- a/handshake_message_certificate_request.go
+++ b/handshake_message_certificate_request.go
@@ -76,7 +76,7 @@ func (h *handshakeMessageCertificateRequest) Unmarshal(data []byte) error {
 		if len(data) < (offset + i + 2) {
 			return errBufferTooSmall
 		}
-		hash := HashAlgorithm(data[offset+i])
+		hash := hashAlgorithm(data[offset+i])
 		signature := signatureAlgorithm(data[offset+i+1])
 
 		if _, ok := hashAlgorithms[hash]; !ok {
diff --git a/handshake_message_certificate_verify.go b/handshake_message_certificate_verify.go
index dca6426..2a45eb4 100644
--- a/handshake_message_certificate_verify.go
+++ b/handshake_message_certificate_verify.go
@@ -5,7 +5,7 @@ import (
 )
 
 type handshakeMessageCertificateVerify struct {
-	hashAlgorithm      HashAlgorithm
+	hashAlgorithm      hashAlgorithm
 	signatureAlgorithm signatureAlgorithm
 	signature          []byte
 }
@@ -31,7 +31,7 @@ func (h *handshakeMessageCertificateVerify) Unmarshal(data []byte) error {
 		return errBufferTooSmall
 	}
 
-	h.hashAlgorithm = HashAlgorithm(data[0])
+	h.hashAlgorithm = hashAlgorithm(data[0])
 	if _, ok := hashAlgorithms[h.hashAlgorithm]; !ok {
 		return errInvalidHashAlgorithm
 	}
diff --git a/handshake_message_server_key_exchange.go b/handshake_message_server_key_exchange.go
index 33bb03e..6d01158 100644
--- a/handshake_message_server_key_exchange.go
+++ b/handshake_message_server_key_exchange.go
@@ -11,7 +11,7 @@ type handshakeMessageServerKeyExchange struct {
 	ellipticCurveType  ellipticCurveType
 	namedCurve         namedCurve
 	publicKey          []byte
-	hashAlgorithm      HashAlgorithm
+	hashAlgorithm      hashAlgorithm
 	signatureAlgorithm signatureAlgorithm
 	signature          []byte
 }
@@ -78,7 +78,7 @@ func (h *handshakeMessageServerKeyExchange) Unmarshal(data []byte) error {
 	if len(data) <= offset {
 		return errBufferTooSmall
 	}
-	h.hashAlgorithm = HashAlgorithm(data[offset])
+	h.hashAlgorithm = hashAlgorithm(data[offset])
 	if _, ok := hashAlgorithms[h.hashAlgorithm]; !ok {
 		return errInvalidHashAlgorithm
 	}
diff --git a/hash_algorithm.go b/hash_algorithm.go
index 8da821b..ef18343 100644
--- a/hash_algorithm.go
+++ b/hash_algorithm.go
@@ -8,79 +8,59 @@ import (
 	"crypto/sha512"
 )
 
-// HashAlgorithm is used to indicate the hash algorithm used
+// hashAlgorithm is used to indicate the hash algorithm used
 // https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-18
-type HashAlgorithm uint16
+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
+	// hashAlgorithmMD2    hashAlgorithm = 0 // Blacklisted
+	hashAlgorithmMD5    hashAlgorithm = 1 // Blacklisted
+	hashAlgorithmSHA1   hashAlgorithm = 2 // Blacklisted
+	hashAlgorithmSHA224 hashAlgorithm = 3
+	hashAlgorithmSHA256 hashAlgorithm = 4
+	hashAlgorithmSHA384 hashAlgorithm = 5
+	hashAlgorithmSHA512 hashAlgorithm = 6
 )
 
-// String makes HashAlgorithm printable
-func (h HashAlgorithm) String() string {
+// String makes hashAlgorithm printable
+func (h hashAlgorithm) String() string {
 	switch h {
-	case HashAlgorithmMD5:
+	case hashAlgorithmMD5:
 		return "md5" // [RFC3279]
-	case HashAlgorithmSHA1:
+	case hashAlgorithmSHA1:
 		return "sha-1" // [RFC3279]
-	case HashAlgorithmSHA224:
+	case hashAlgorithmSHA224:
 		return "sha-224" // [RFC4055]
-	case HashAlgorithmSHA256:
+	case hashAlgorithmSHA256:
 		return "sha-256" // [RFC4055]
-	case HashAlgorithmSHA384:
+	case hashAlgorithmSHA384:
 		return "sha-384" // [RFC4055]
-	case HashAlgorithmSHA512:
+	case hashAlgorithmSHA512:
 		return "sha-512" // [RFC4055]
 	default:
 		return "unknown hash algorithm"
 	}
 }
 
-// HashAlgorithmString allows looking up a HashAlgorithm by it's string representation
-func HashAlgorithmString(s string) (HashAlgorithm, error) {
-	switch s {
-	case "md5":
-		return HashAlgorithmMD5, nil // [RFC3279]
-	case "sha-1":
-		return HashAlgorithmSHA1, nil // [RFC3279]
-	case "sha-224":
-		return HashAlgorithmSHA224, nil // [RFC4055]
-	case "sha-256":
-		return HashAlgorithmSHA256, nil // [RFC4055]
-	case "sha-384":
-		return HashAlgorithmSHA384, nil // [RFC4055]
-	case "sha-512":
-		return HashAlgorithmSHA512, nil // [RFC4055]
-	default:
-		return 0, errInvalidHashAlgorithm
-	}
-}
-
-func (h HashAlgorithm) digest(b []byte) []byte {
+func (h hashAlgorithm) digest(b []byte) []byte {
 	switch h {
-	case HashAlgorithmMD5:
+	case hashAlgorithmMD5:
 		hash := md5.Sum(b) // #nosec
 		return hash[:]
-	case HashAlgorithmSHA1:
+	case hashAlgorithmSHA1:
 		hash := sha1.Sum(b) // #nosec
 		return hash[:]
-	case HashAlgorithmSHA224:
+	case hashAlgorithmSHA224:
 		hash := sha256.Sum224(b)
 		return hash[:]
-	case HashAlgorithmSHA256:
+	case hashAlgorithmSHA256:
 		hash := sha256.Sum256(b)
 		return hash[:]
-	case HashAlgorithmSHA384:
+	case hashAlgorithmSHA384:
 		hash := sha512.Sum384(b)
 		return hash[:]
-	case HashAlgorithmSHA512:
+	case hashAlgorithmSHA512:
 		hash := sha512.Sum512(b)
 		return hash[:]
 	default:
@@ -88,30 +68,30 @@ func (h HashAlgorithm) digest(b []byte) []byte {
 	}
 }
 
-func (h HashAlgorithm) cryptoHash() crypto.Hash {
+func (h hashAlgorithm) cryptoHash() crypto.Hash {
 	switch h {
-	case HashAlgorithmMD5:
+	case hashAlgorithmMD5:
 		return crypto.MD5
-	case HashAlgorithmSHA1:
+	case hashAlgorithmSHA1:
 		return crypto.SHA1
-	case HashAlgorithmSHA224:
+	case hashAlgorithmSHA224:
 		return crypto.SHA224
-	case HashAlgorithmSHA256:
+	case hashAlgorithmSHA256:
 		return crypto.SHA256
-	case HashAlgorithmSHA384:
+	case hashAlgorithmSHA384:
 		return crypto.SHA384
-	case HashAlgorithmSHA512:
+	case hashAlgorithmSHA512:
 		return crypto.SHA512
 	default:
 		return 0
 	}
 }
 
-var hashAlgorithms = map[HashAlgorithm]struct{}{
-	HashAlgorithmMD5:    {},
-	HashAlgorithmSHA1:   {},
-	HashAlgorithmSHA224: {},
-	HashAlgorithmSHA256: {},
-	HashAlgorithmSHA384: {},
-	HashAlgorithmSHA512: {},
+var hashAlgorithms = map[hashAlgorithm]struct{}{
+	hashAlgorithmMD5:    {},
+	hashAlgorithmSHA1:   {},
+	hashAlgorithmSHA224: {},
+	hashAlgorithmSHA256: {},
+	hashAlgorithmSHA384: {},
+	hashAlgorithmSHA512: {},
 }
diff --git a/pkg/crypto/fingerprint/fingerprint.go b/pkg/crypto/fingerprint/fingerprint.go
new file mode 100644
index 0000000..215b44e
--- /dev/null
+++ b/pkg/crypto/fingerprint/fingerprint.go
@@ -0,0 +1,50 @@
+// Package fingerprint provides a helper to create fingerprint string from certificate
+package fingerprint
+
+import (
+	"crypto"
+	"crypto/x509"
+	"errors"
+	"fmt"
+)
+
+var (
+	errHashUnavailable          = errors.New("fingerprint: hash algorithm is not linked into the binary")
+	errInvalidFingerprintLength = errors.New("fingerprint: invalid fingerprint length")
+)
+
+// Fingerprint creates a fingerprint for a certificate using the specified hash algorithm
+func Fingerprint(cert *x509.Certificate, algo crypto.Hash) (string, error) {
+	if !algo.Available() {
+		return "", errHashUnavailable
+	}
+	h := algo.New()
+	for i := 0; i < len(cert.Raw); {
+		n, _ := h.Write(cert.Raw[i:])
+		// Hash.Writer is specified to be never returning an error.
+		// https://golang.org/pkg/hash/#Hash
+		i += n
+	}
+	digest := []byte(fmt.Sprintf("%x", h.Sum(nil)))
+
+	digestlen := len(digest)
+	if digestlen == 0 {
+		return "", nil
+	}
+	if digestlen%2 != 0 {
+		return "", errInvalidFingerprintLength
+	}
+	res := make([]byte, digestlen>>1+digestlen-1)
+
+	pos := 0
+	for i, c := range digest {
+		res[pos] = c
+		pos++
+		if (i)%2 != 0 && i < digestlen-1 {
+			res[pos] = byte(':')
+			pos++
+		}
+	}
+
+	return string(res), nil
+}
diff --git a/pkg/crypto/fingerprint/hash.go b/pkg/crypto/fingerprint/hash.go
new file mode 100644
index 0000000..7f336b8
--- /dev/null
+++ b/pkg/crypto/fingerprint/hash.go
@@ -0,0 +1,25 @@
+package fingerprint
+
+import (
+	"crypto"
+	"errors"
+)
+
+var errInvalidHashAlgorithm = errors.New("fingerprint: invalid hash algorithm")
+
+var nameToHash = map[string]crypto.Hash{
+	"md5":     crypto.MD5,    // [RFC3279]
+	"sha-1":   crypto.SHA1,   // [RFC3279]
+	"sha-224": crypto.SHA224, // [RFC4055]
+	"sha-256": crypto.SHA256, // [RFC4055]
+	"sha-384": crypto.SHA384, // [RFC4055]
+	"sha-512": crypto.SHA512, // [RFC4055]
+}
+
+// HashFromString allows looking up a hash algorithm by it's string representation
+func HashFromString(s string) (crypto.Hash, error) {
+	if h, ok := nameToHash[s]; ok {
+		return h, nil
+	}
+	return 0, errInvalidHashAlgorithm
+}
diff --git a/server_handlers.go b/server_handlers.go
index ab50882..30312c7 100644
--- a/server_handlers.go
+++ b/server_handlers.go
@@ -380,7 +380,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.localCertificates[0].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
 				}
@@ -400,7 +400,7 @@ func serverFlightHandler(c *Conn) (bool, *alert, error) {
 							ellipticCurveType:  ellipticCurveTypeNamedCurve,
 							namedCurve:         c.namedCurve,
 							publicKey:          c.localKeypair.publicKey,
-							hashAlgorithm:      HashAlgorithmSHA256,
+							hashAlgorithm:      hashAlgorithmSHA256,
 							signatureAlgorithm: signatureAlgorithmECDSA,
 							signature:          c.localKeySignature,
 						}},
@@ -423,12 +423,12 @@ func serverFlightHandler(c *Conn) (bool, *alert, error) {
 							handshakeMessage: &handshakeMessageCertificateRequest{
 								certificateTypes: []clientCertificateType{clientCertificateTypeRSASign, clientCertificateTypeECDSASign},
 								signatureHashAlgorithms: []signatureHashAlgorithm{
-									{HashAlgorithmSHA256, signatureAlgorithmRSA},
-									{HashAlgorithmSHA384, signatureAlgorithmRSA},
-									{HashAlgorithmSHA512, signatureAlgorithmRSA},
-									{HashAlgorithmSHA256, signatureAlgorithmECDSA},
-									{HashAlgorithmSHA384, signatureAlgorithmECDSA},
-									{HashAlgorithmSHA512, signatureAlgorithmECDSA},
+									{hashAlgorithmSHA256, signatureAlgorithmRSA},
+									{hashAlgorithmSHA384, signatureAlgorithmRSA},
+									{hashAlgorithmSHA512, signatureAlgorithmRSA},
+									{hashAlgorithmSHA256, signatureAlgorithmECDSA},
+									{hashAlgorithmSHA384, signatureAlgorithmECDSA},
+									{hashAlgorithmSHA512, signatureAlgorithmECDSA},
 								},
 							},
 						},
diff --git a/signature_hash_algorithm.go b/signature_hash_algorithm.go
index 6a2f0ea..3f0f9e7 100644
--- a/signature_hash_algorithm.go
+++ b/signature_hash_algorithm.go
@@ -1,6 +1,6 @@
 package dtls
 
 type signatureHashAlgorithm struct {
-	hash      HashAlgorithm
+	hash      hashAlgorithm
 	signature signatureAlgorithm
 }


Test patch:
diff --git a/conn_test.go b/conn_test.go
index 1dbafb3..228c2b3 100644
--- a/conn_test.go
+++ b/conn_test.go
@@ -1016,12 +1016,12 @@ func TestServerTimeout(t *testing.T) {
 	extensions := []extension{
 		&extensionSupportedSignatureAlgorithms{
 			signatureHashAlgorithms: []signatureHashAlgorithm{
-				{HashAlgorithmSHA256, signatureAlgorithmECDSA},
-				{HashAlgorithmSHA384, signatureAlgorithmECDSA},
-				{HashAlgorithmSHA512, signatureAlgorithmECDSA},
-				{HashAlgorithmSHA256, signatureAlgorithmRSA},
-				{HashAlgorithmSHA384, signatureAlgorithmRSA},
-				{HashAlgorithmSHA512, signatureAlgorithmRSA},
+				{hashAlgorithmSHA256, signatureAlgorithmECDSA},
+				{hashAlgorithmSHA384, signatureAlgorithmECDSA},
+				{hashAlgorithmSHA512, signatureAlgorithmECDSA},
+				{hashAlgorithmSHA256, signatureAlgorithmRSA},
+				{hashAlgorithmSHA384, signatureAlgorithmRSA},
+				{hashAlgorithmSHA512, signatureAlgorithmRSA},
 			},
 		},
 		&extensionSupportedEllipticCurves{
diff --git a/crypto_test.go b/crypto_test.go
index e973fb5..be42894 100644
--- a/crypto_test.go
+++ b/crypto_test.go
@@ -61,7 +61,7 @@ func TestGenerateKeySignature(t *testing.T) {
 		0x87, 0x5e, 0x5c, 0x36, 0x75, 0x86,
 	}
 
-	signature, err := generateKeySignature(clientRandom, serverRandom, publicKey, namedCurveX25519, key, HashAlgorithmSHA256)
+	signature, err := generateKeySignature(clientRandom, serverRandom, publicKey, namedCurveX25519, key, hashAlgorithmSHA256)
 	if err != nil {
 		t.Error(err)
 	} else if !bytes.Equal(expectedSignature, signature) {
diff --git a/extension_supported_signature_algorithms_test.go b/extension_supported_signature_algorithms_test.go
index 9b20bf2..e231f0a 100644
--- a/extension_supported_signature_algorithms_test.go
+++ b/extension_supported_signature_algorithms_test.go
@@ -16,9 +16,9 @@ func TestExtensionSupportedSignatureAlgorithms(t *testing.T) {
 	}
 	parsedExtensionSupportedSignatureAlgorithms := &extensionSupportedSignatureAlgorithms{
 		signatureHashAlgorithms: []signatureHashAlgorithm{
-			{HashAlgorithmSHA256, signatureAlgorithmECDSA},
-			{HashAlgorithmSHA384, signatureAlgorithmECDSA},
-			{HashAlgorithmSHA512, signatureAlgorithmECDSA},
+			{hashAlgorithmSHA256, signatureAlgorithmECDSA},
+			{hashAlgorithmSHA384, signatureAlgorithmECDSA},
+			{hashAlgorithmSHA512, signatureAlgorithmECDSA},
 		},
 	}
 
diff --git a/handshake_message_certificate_request_test.go b/handshake_message_certificate_request_test.go
index 5c86668..befd25a 100644
--- a/handshake_message_certificate_request_test.go
+++ b/handshake_message_certificate_request_test.go
@@ -16,12 +16,12 @@ func TestHandshakeMessageCertificateRequest(t *testing.T) {
 			clientCertificateTypeECDSASign,
 		},
 		signatureHashAlgorithms: []signatureHashAlgorithm{
-			{hash: HashAlgorithmSHA256, signature: signatureAlgorithmECDSA},
-			{hash: HashAlgorithmSHA256, signature: signatureAlgorithmRSA},
-			{hash: HashAlgorithmSHA384, signature: signatureAlgorithmECDSA},
-			{hash: HashAlgorithmSHA384, signature: signatureAlgorithmRSA},
-			{hash: HashAlgorithmSHA512, signature: signatureAlgorithmRSA},
-			{hash: HashAlgorithmSHA1, signature: signatureAlgorithmRSA},
+			{hash: hashAlgorithmSHA256, signature: signatureAlgorithmECDSA},
+			{hash: hashAlgorithmSHA256, signature: signatureAlgorithmRSA},
+			{hash: hashAlgorithmSHA384, signature: signatureAlgorithmECDSA},
+			{hash: hashAlgorithmSHA384, signature: signatureAlgorithmRSA},
+			{hash: hashAlgorithmSHA512, signature: signatureAlgorithmRSA},
+			{hash: hashAlgorithmSHA1, signature: signatureAlgorithmRSA},
 		},
 	}
 
diff --git a/handshake_message_certificate_verify_test.go b/handshake_message_certificate_verify_test.go
index e166864..3adaeb0 100644
--- a/handshake_message_certificate_verify_test.go
+++ b/handshake_message_certificate_verify_test.go
@@ -14,7 +14,7 @@ func TestHandshakeMessageCertificateVerify(t *testing.T) {
 		0xbb, 0x03, 0x0e, 0xf1, 0xcb, 0x28, 0x22, 0x33, 0x23, 0x88, 0xad,
 	}
 	parsedCertificateVerify := &handshakeMessageCertificateVerify{
-		hashAlgorithm:      HashAlgorithm(rawCertificateVerify[0]),
+		hashAlgorithm:      hashAlgorithm(rawCertificateVerify[0]),
 		signatureAlgorithm: signatureAlgorithm(rawCertificateVerify[1]),
 		signature:          rawCertificateVerify[4:],
 	}
diff --git a/handshake_message_server_key_exchange_test.go b/handshake_message_server_key_exchange_test.go
index 8ddec1e..be4ce31 100644
--- a/handshake_message_server_key_exchange_test.go
+++ b/handshake_message_server_key_exchange_test.go
@@ -21,7 +21,7 @@ func TestHandshakeMessageServerKeyExchange(t *testing.T) {
 		ellipticCurveType:  ellipticCurveTypeNamedCurve,
 		namedCurve:         namedCurveX25519,
 		publicKey:          rawServerKeyExchange[4:69],
-		hashAlgorithm:      HashAlgorithmSHA1,
+		hashAlgorithm:      hashAlgorithmSHA1,
 		signatureAlgorithm: signatureAlgorithmECDSA,
 		signature:          rawServerKeyExchange[73:144],
 	}
diff --git a/hash_algorithm_test.go b/hash_algorithm_test.go
new file mode 100644
index 0000000..d6c3603
--- /dev/null
+++ b/hash_algorithm_test.go
@@ -0,0 +1,21 @@
+package dtls
+
+import (
+	"testing"
+
+	"github.com/pion/dtls/v2/pkg/crypto/fingerprint"
+)
+
+func TestHashAlgorithm_StringRoundtrip(t *testing.T) {
+	for algo := range hashAlgorithms {
+		str := algo.String()
+		hash1 := algo.cryptoHash()
+		hash2, err := fingerprint.HashFromString(str)
+		if err != nil {
+			t.Fatalf("fingerprint.HashFromString failed: %v", err)
+		}
+		if hash1 != hash2 {
+			t.Errorf("Hash algorithm mismatch, input: %d, after roundtrip: %d", int(hash1), int(hash2))
+		}
+	}
+}
diff --git a/fingerprint_test.go b/pkg/crypto/fingerprint/fingerprint_test.go
similarity index 89%
rename from fingerprint_test.go
rename to pkg/crypto/fingerprint/fingerprint_test.go
index 96ce4b8..0b1e9c9 100644
--- a/fingerprint_test.go
+++ b/pkg/crypto/fingerprint/fingerprint_test.go
@@ -1,6 +1,7 @@
-package dtls
+package fingerprint
 
 import (
+	"crypto"
 	"crypto/x509"
 	"testing"
 )
@@ -32,10 +33,17 @@ func TestFingerprint(t *testing.T) {
 	}
 
 	const expectedSHA256 = "60:ef:f5:79:ad:8d:3e:d7:e8:4d:5a:5a:d6:1e:71:2d:47:52:a5:cb:df:34:37:87:10:a5:4e:d7:2a:2c:37:34"
-	actualSHA256, err := Fingerprint(cert, HashAlgorithmSHA256)
+	actualSHA256, err := Fingerprint(cert, crypto.SHA256)
 	if err != nil {
 		t.Fatal(err)
 	} else if actualSHA256 != expectedSHA256 {
 		t.Fatalf("Fingerprint SHA256 mismatch expected(%s) actual(%s)", expectedSHA256, actualSHA256)
 	}
 }
+
+func TestFingerprint_UnavailableHash(t *testing.T) {
+	_, err := Fingerprint(&x509.Certificate{}, crypto.Hash(0xFFFFFFFF))
+	if err != errHashUnavailable {
+		t.Errorf("Expected error '%v' for invalid hash ID, got '%v'", errHashUnavailable, err)
+	}
+}
diff --git a/pkg/crypto/fingerprint/hash_test.go b/pkg/crypto/fingerprint/hash_test.go
new file mode 100644
index 0000000..1280252
--- /dev/null
+++ b/pkg/crypto/fingerprint/hash_test.go
@@ -0,0 +1,24 @@
+package fingerprint
+
+import (
+	"crypto"
+	"testing"
+)
+
+func TestHashFromString(t *testing.T) {
+	t.Run("InvalidHashAlgorithm", func(t *testing.T) {
+		_, err := HashFromString("invalid-hash-algorithm")
+		if err != errInvalidHashAlgorithm {
+			t.Errorf("Expected error '%v' for invalid hash name, got '%v'", errInvalidHashAlgorithm, err)
+		}
+	})
+	t.Run("ValidHashAlgorithm", func(t *testing.T) {
+		h, err := HashFromString("sha-512")
+		if err != nil {
+			t.Fatalf("Unexpected error for valid hash name, got '%v'", err)
+		}
+		if h != crypto.SHA512 {
+			t.Errorf("Expected hash ID of %d, got %d", int(crypto.SHA512), int(h))
+		}
+	})
+}