Repository: go
Issue: proposal: crypto: support 3rd-party hash algorithms
Labels: Proposal,Proposal-Crypto

### Proposal Details

`crypto/rsa.SignPSS()` and similar packages take a `crypto.Hash`, which is hard-coded to only support known-to-Go hash algorithms.
This is a total show-stopper for me, because I need to be able to support new, not-yet-existing hash algorithms.
I propose a new implementation for `src/crypto/crypto.go`:
```go
// ...

type customImplementation struct {
	name string

	// int is used rather than uint8 of digestSizes in order to support future
	// hash functions with large digests.
	digestSize int

	f func() hash.Hash
}

var customImplementations = make(map[Hash]customImplementation)

func (h Hash) String() string {
	switch h {
	// ...
	default:
		if implementation, exists := customImplementations[h]; exists {
			return implementation.name
		}
		return "unknown hash value " + strconv.Itoa(int(h))
	}
}

// ...

// Defines the limit of 1st-party hash functions.
const boundaryFirstParty Hash = 101

// ...

// Size returns the length, in bytes, of a digest resulting from the given hash
// function. It doesn't require that the hash function in question be linked
// into the program for hash functions known to Go authors. It does require
// that the hash function in question be linked into the program for 3rd-party
// hash functions.
func (h Hash) Size() int {
	if h < 1 || h == maxHash {
		panic("crypto: Size of unknown hash function")
	}
	if h < maxHash {
		return int(digestSizes[h])
	}
	if implementation, exists := customImplementations[h]; exists {
		return implementation.digestSize
	}
	panic("crypto: Size of unknown hash function")
}

// ...

// New returns a new hash.Hash calculating the given hash function. New panics
// if the hash function is not linked into the binary.
func (h Hash) New() hash.Hash {
	if h < 1 || h == maxHash {
		panic("crypto: requested hash function #" + strconv.Itoa(int(h)) + " is unavailable")
	}
	if h < maxHash {
		f := hashes[h]
		if f != nil {
			return f()
		}
	}
	if implementation, exists := customImplementations[h]; exists {
		return implementation.f()
	}
	panic("crypto: requested hash function #" + strconv.Itoa(int(h)) + " is unavailable")
}

// Available reports whether the given hash function is linked into the binary.
func (h Hash) Available() bool {
	if h < maxHash && hashes[h] != nil {
		return true
	}
	if _, exists := customImplementations[h]; exists {
		return true
	}
	return false
}

// ...

// RegisterCustomHash registers a function that returns a new instance of the given
// 3rd-party hash function. This is intended to be called from the init function in
// packages that implement hash functions. Registering hash functions outside of an
// init function can cause thread-unsafe behaviors.
func RegisterCustomHash(h Hash, name string, digestSize int, f func() hash.Hash) error {
	if h <= boundaryFirstParty {
		return errors.New("crypto: RegisterCustomHash within 1st-party region")
	}
	if len(name) == 0 {
		return errors.New("crypto: RegisterCustomHash of hash function without a name")
	}
	if digestSize < 1 {
		return errors.New("crypto: RegisterCustomHash of hash function with empty digests")
	}
	if f == nil {
		return errors.New("crypto: RegisterCustomHash of hash function without an allocator")
	}
	if _, exists := customImplementations[h]; exists {
		return errors.New("crypto: RegisterCustomHash of an existing hash function")
	}
	customImplementations[h] = customImplementation{
		name: name,
		digestSize: digestSize,
		f: f,
	}
	return nil
}

// ...
```
(note that I would submit a pull request, but I am currently on a very shoddy hotspot internet connection that prevents me from performing the necessary tasks to do so.)

There is of course an issue with this proposal: 3rd-party hash algorithms might use the same value for `crypto.Hash`, which would lead to errors.
Ideally we would change the type definition of `crypto.Hash` to something more sensible than a `uint`, but this would break packages that use the `uint` to identify hash algorithms.
Perhaps a checksum of the hash algorithm's name to produce a `uint` would be better?