libauth/libauth.go

85 lines
2.6 KiB
Go

package libauth
import (
"fmt"
"net/http"
"strings"
"git.rootprojects.org/root/keypairs"
"git.rootprojects.org/root/keypairs/keyfetch"
)
// JWS is keypairs.JWS with added debugging information
type JWS struct {
keypairs.JWS
Trusted bool `json:"trusted"`
Errors []error `json:"errors,omitempty"`
}
// VerifyJWT will return a verified InspectableToken if possible, or otherwise as much detail as possible, possibly including an InspectableToken with failed verification.
func VerifyJWT(jwt string, issuers keyfetch.Whitelist, r *http.Request) (*JWS, error) {
jws := keypairs.JWTToJWS(jwt)
if nil == jws {
return nil, fmt.Errorf("Bad Request: malformed Authorization header")
}
if err := jws.DecodeComponents(); nil != err {
return &JWS{
*jws,
false,
[]error{err},
}, err
}
return VerifyJWS(jws, issuers, r)
}
// VerifyJWS takes a fully decoded JWS and will return a verified InspectableToken if possible, or otherwise as much detail as possible, possibly including an InspectableToken with failed verification.
func VerifyJWS(jws *keypairs.JWS, issuers keyfetch.Whitelist, r *http.Request) (*JWS, error) {
var pub keypairs.PublicKey
kid, kidOK := jws.Header["kid"].(string)
iss, issOK := jws.Claims["iss"].(string)
_, jwkOK := jws.Header["jwk"]
if !jwkOK {
if !kidOK || 0 == len(kid) {
//errs = append(errs, "must have either header.kid or header.jwk")
return nil, fmt.Errorf("Bad Request: missing 'kid' identifier")
} else if !issOK || 0 == len(iss) {
//errs = append(errs, "payload.iss must exist to complement header.kid")
return nil, fmt.Errorf("Bad Request: payload.iss must exist to complement header.kid")
} else {
// TODO beware domain fronting, we should set domain statically
// See https://pkg.go.dev/git.rootprojects.org/root/keypairs@v0.6.2/keyfetch
// (Caddy does protect against Domain-Fronting by default:
// https://github.com/caddyserver/caddy/issues/2500)
if !issuers.IsTrustedIssuer(iss, r) {
return nil, fmt.Errorf("Bad Request: 'iss' is not a trusted issuer")
}
}
var err error
pub, err = keyfetch.OIDCJWK(kid, iss)
if nil != err {
return nil, fmt.Errorf("Bad Request: 'kid' could not be matched to a known public key: %w", err)
}
} else {
return nil, fmt.Errorf("Bad Request: self-signed tokens with 'jwk' are not supported")
}
errs := keypairs.VerifyClaims(pub, jws)
if 0 != len(errs) {
strs := []string{}
for _, err := range errs {
strs = append(strs, err.Error())
}
return nil, fmt.Errorf("invalid jwt:\n%s", strings.Join(strs, "\n\t"))
}
return &JWS{
JWS: *jws,
Trusted: true,
Errors: nil,
}, nil
}