mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2026-10-07 07:01:24 +02:00
fix: hmacauth dependency licensing issue (#3253)
* fix: upstream licensing issue by adopting hmacauth library and changing asserting library for its test cases Signed-off-by: Jan Larwig <jan@larwig.com> * fix: golang code quality and linting issues for hmacauth Signed-off-by: Jan Larwig <jan@larwig.com> --------- Signed-off-by: Jan Larwig <jan@larwig.com>
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
package hmacauth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/hmac"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// HmacAuth signs outbound requests and authenticates inbound requests.
|
||||
type HmacAuth interface {
|
||||
// Produces the string that will be prefixed to the request body and
|
||||
// used to generate the signature.
|
||||
StringToSign(req *http.Request) string
|
||||
|
||||
// Adds a signature header to the request.
|
||||
SignRequest(req *http.Request)
|
||||
|
||||
// Generates a signature for the request.
|
||||
RequestSignature(req *http.Request) string
|
||||
|
||||
// Retrieves the signature included in the request header.
|
||||
SignatureFromHeader(req *http.Request) string
|
||||
|
||||
// Authenticates the request, returning the result code, the signature
|
||||
// from the header, and the locally-computed signature.
|
||||
AuthenticateRequest(request *http.Request) (
|
||||
result AuthenticationResult,
|
||||
headerSignature, computedSignature string)
|
||||
}
|
||||
|
||||
var supportedAlgorithms = map[string]crypto.Hash{
|
||||
"md4": crypto.MD4,
|
||||
"md5": crypto.MD5,
|
||||
"sha1": crypto.SHA1,
|
||||
"sha224": crypto.SHA224,
|
||||
"sha256": crypto.SHA256,
|
||||
"sha384": crypto.SHA384,
|
||||
"sha512": crypto.SHA512,
|
||||
"ripemd160": crypto.RIPEMD160,
|
||||
}
|
||||
|
||||
var algorithmName map[crypto.Hash]string
|
||||
|
||||
func init() {
|
||||
algorithmName = make(map[crypto.Hash]string)
|
||||
for name, algorithm := range supportedAlgorithms {
|
||||
algorithmName[algorithm] = name
|
||||
// Make sure the algorithm is linked into the binary, per
|
||||
// https://golang.org/pkg/crypto/#Hash.Available
|
||||
//
|
||||
// Note that both sides of the client/server connection must
|
||||
// have an algorithm available in order to successfully
|
||||
// authenticate using that algorithm
|
||||
if !algorithm.Available() {
|
||||
delete(supportedAlgorithms, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DigestNameToCryptoHash returns the crypto.Hash value corresponding to the
|
||||
// algorithm name, or an error if the algorithm is not supported.
|
||||
func DigestNameToCryptoHash(name string) (result crypto.Hash, err error) {
|
||||
var supported bool
|
||||
if result, supported = supportedAlgorithms[name]; !supported {
|
||||
err = errors.New("hmacauth: hash algorithm not supported: " +
|
||||
name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CryptoHashToDigestName returns the algorithm name corresponding to the
|
||||
// crypto.Hash ID, or an error if the algorithm is not supported.
|
||||
func CryptoHashToDigestName(id crypto.Hash) (result string, err error) {
|
||||
var supported bool
|
||||
if result, supported = algorithmName[id]; !supported {
|
||||
err = fmt.Errorf("hmacauth: unsupported crypto.Hash #%d", id)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type hmacAuth struct {
|
||||
hash crypto.Hash
|
||||
key []byte
|
||||
header string
|
||||
headers []string
|
||||
}
|
||||
|
||||
// NewHmacAuth returns an HmacAuth object that can be used to sign or
|
||||
// authenticate HTTP requests based on the supplied parameters.
|
||||
func NewHmacAuth(hash crypto.Hash, key []byte, header string,
|
||||
headers []string) HmacAuth {
|
||||
if !hash.Available() {
|
||||
var name string
|
||||
var supported bool
|
||||
if name, supported = algorithmName[hash]; !supported {
|
||||
name = fmt.Sprintf("#%d", hash)
|
||||
}
|
||||
panic("hmacauth: hash algorithm " + name + " is unavailable")
|
||||
}
|
||||
canonicalHeaders := make([]string, len(headers))
|
||||
for i, h := range headers {
|
||||
canonicalHeaders[i] = http.CanonicalHeaderKey(h)
|
||||
}
|
||||
return &hmacAuth{hash, key, header, canonicalHeaders}
|
||||
}
|
||||
|
||||
func (auth *hmacAuth) StringToSign(req *http.Request) string {
|
||||
var buffer bytes.Buffer
|
||||
_, _ = buffer.WriteString(req.Method)
|
||||
_, _ = buffer.WriteString("\n")
|
||||
|
||||
for _, header := range auth.headers {
|
||||
values := req.Header[header]
|
||||
lastIndex := len(values) - 1
|
||||
for i, value := range values {
|
||||
_, _ = buffer.WriteString(value)
|
||||
if i != lastIndex {
|
||||
_, _ = buffer.WriteString(",")
|
||||
}
|
||||
}
|
||||
_, _ = buffer.WriteString("\n")
|
||||
}
|
||||
_, _ = buffer.WriteString(req.URL.Path)
|
||||
if req.URL.RawQuery != "" {
|
||||
_, _ = buffer.WriteString("?")
|
||||
_, _ = buffer.WriteString(req.URL.RawQuery)
|
||||
}
|
||||
if req.URL.Fragment != "" {
|
||||
_, _ = buffer.WriteString("#")
|
||||
_, _ = buffer.WriteString(req.URL.Fragment)
|
||||
}
|
||||
_, _ = buffer.WriteString("\n")
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (auth *hmacAuth) SignRequest(req *http.Request) {
|
||||
req.Header.Set(auth.header, auth.RequestSignature(req))
|
||||
}
|
||||
|
||||
func (auth *hmacAuth) RequestSignature(req *http.Request) string {
|
||||
return requestSignature(auth, req, auth.hash)
|
||||
}
|
||||
|
||||
func requestSignature(auth *hmacAuth, req *http.Request,
|
||||
hashAlgorithm crypto.Hash) string {
|
||||
h := hmac.New(hashAlgorithm.New, auth.key)
|
||||
_, _ = h.Write([]byte(auth.StringToSign(req)))
|
||||
|
||||
if req.Body != nil {
|
||||
reqBody, _ := io.ReadAll(req.Body)
|
||||
req.Body = io.NopCloser(bytes.NewBuffer(reqBody))
|
||||
_, _ = h.Write(reqBody)
|
||||
}
|
||||
|
||||
var sig []byte
|
||||
sig = h.Sum(sig)
|
||||
return algorithmName[hashAlgorithm] + " " +
|
||||
base64.StdEncoding.EncodeToString(sig)
|
||||
}
|
||||
|
||||
func (auth *hmacAuth) SignatureFromHeader(req *http.Request) string {
|
||||
return req.Header.Get(auth.header)
|
||||
}
|
||||
|
||||
// AuthenticationResult is a code used to identify the outcome of
|
||||
// HmacAuth.AuthenticateRequest().
|
||||
type AuthenticationResult int
|
||||
|
||||
const (
|
||||
// ResultNoSignature - the incoming result did not have a signature
|
||||
// header.
|
||||
ResultNoSignature AuthenticationResult = iota
|
||||
|
||||
// ResultInvalidFormat - the signature header was not parseable.
|
||||
ResultInvalidFormat
|
||||
|
||||
// ResultUnsupportedAlgorithm - the signature header specified an
|
||||
// unsupported algorithm.
|
||||
ResultUnsupportedAlgorithm
|
||||
|
||||
// ResultMatch - the signature from the request header matched the
|
||||
// locally-computed signature.
|
||||
ResultMatch
|
||||
|
||||
// ResultMismatch - the signature from the request header did not match
|
||||
// the locally-computed signature.
|
||||
ResultMismatch
|
||||
)
|
||||
|
||||
var validationResultStrings = []string{
|
||||
"",
|
||||
"ResultNoSignature",
|
||||
"ResultInvalidFormat",
|
||||
"ResultUnsupportedAlgorithm",
|
||||
"ResultMatch",
|
||||
"ResultMismatch",
|
||||
}
|
||||
|
||||
func (result AuthenticationResult) String() string {
|
||||
return validationResultStrings[result]
|
||||
}
|
||||
|
||||
func (auth *hmacAuth) AuthenticateRequest(request *http.Request) (
|
||||
result AuthenticationResult, headerSignature,
|
||||
computedSignature string) {
|
||||
headerSignature = auth.SignatureFromHeader(request)
|
||||
if headerSignature == "" {
|
||||
result = ResultNoSignature
|
||||
return
|
||||
}
|
||||
|
||||
components := strings.Split(headerSignature, " ")
|
||||
if len(components) != 2 {
|
||||
result = ResultInvalidFormat
|
||||
return
|
||||
}
|
||||
|
||||
algorithm, err := DigestNameToCryptoHash(components[0])
|
||||
if err != nil {
|
||||
result = ResultUnsupportedAlgorithm
|
||||
return
|
||||
}
|
||||
|
||||
computedSignature = requestSignature(auth, request, algorithm)
|
||||
if hmac.Equal([]byte(headerSignature), []byte(computedSignature)) {
|
||||
result = ResultMatch
|
||||
} else {
|
||||
result = ResultMismatch
|
||||
}
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user