open-vault/vendor/github.com/briankassouf/jose/jws/jws.go
2018-06-19 23:03:22 -04:00

491 lines
12 KiB
Go

package jws
import (
"bytes"
"encoding/json"
"net/http"
"strings"
"github.com/briankassouf/jose"
"github.com/briankassouf/jose/crypto"
)
// JWS implements a JWS per RFC 7515.
type JWS interface {
// Payload Returns the payload.
Payload() interface{}
// SetPayload sets the payload with the given value.
SetPayload(p interface{})
// Protected returns the JWS' Protected Header.
Protected() jose.Protected
// ProtectedAt returns the JWS' Protected Header.
// i represents the index of the Protected Header.
ProtectedAt(i int) jose.Protected
// Header returns the JWS' unprotected Header.
Header() jose.Header
// HeaderAt returns the JWS' unprotected Header.
// i represents the index of the unprotected Header.
HeaderAt(i int) jose.Header
// Verify validates the current JWS' signature as-is. Refer to
// ValidateMulti for more information.
Verify(key interface{}, method crypto.SigningMethod) error
// ValidateMulti validates the current JWS' signature as-is. Since it's
// meant to be called after parsing a stream of bytes into a JWS, it
// shouldn't do any internal parsing like the Sign, Flat, Compact, or
// General methods do.
VerifyMulti(keys []interface{}, methods []crypto.SigningMethod, o *SigningOpts) error
// VerifyCallback validates the current JWS' signature as-is. It
// accepts a callback function that can be used to access header
// parameters to lookup needed information. For example, looking
// up the "kid" parameter.
// The return slice must be a slice of keys used in the verification
// of the JWS.
VerifyCallback(fn VerifyCallback, methods []crypto.SigningMethod, o *SigningOpts) error
// General serializes the JWS into its "general" form per
// https://tools.ietf.org/html/rfc7515#section-7.2.1
General(keys ...interface{}) ([]byte, error)
// Flat serializes the JWS to its "flattened" form per
// https://tools.ietf.org/html/rfc7515#section-7.2.2
Flat(key interface{}) ([]byte, error)
// Compact serializes the JWS into its "compact" form per
// https://tools.ietf.org/html/rfc7515#section-7.1
Compact(key interface{}) ([]byte, error)
// IsJWT returns true if the JWS is a JWT.
IsJWT() bool
}
// jws represents a specific jws.
type jws struct {
payload *payload
plcache rawBase64
clean bool
sb []sigHead
isJWT bool
}
// Payload returns the jws' payload.
func (j *jws) Payload() interface{} {
return j.payload.v
}
// SetPayload sets the jws' raw, unexported payload.
func (j *jws) SetPayload(val interface{}) {
j.payload.v = val
}
// Protected returns the JWS' Protected Header.
func (j *jws) Protected() jose.Protected {
return j.sb[0].protected
}
// Protected returns the JWS' Protected Header.
// i represents the index of the Protected Header.
// Left empty, it defaults to 0.
func (j *jws) ProtectedAt(i int) jose.Protected {
return j.sb[i].protected
}
// Header returns the JWS' unprotected Header.
func (j *jws) Header() jose.Header {
return j.sb[0].unprotected
}
// HeaderAt returns the JWS' unprotected Header.
// |i| is the index of the unprotected Header.
func (j *jws) HeaderAt(i int) jose.Header {
return j.sb[i].unprotected
}
// sigHead represents the 'signatures' member of the jws' "general"
// serialization form per
// https://tools.ietf.org/html/rfc7515#section-7.2.1
//
// It's embedded inside the "flat" structure in order to properly
// create the "flat" jws.
type sigHead struct {
Protected rawBase64 `json:"protected,omitempty"`
Unprotected rawBase64 `json:"header,omitempty"`
Signature crypto.Signature `json:"signature"`
protected jose.Protected
unprotected jose.Header
clean bool
method crypto.SigningMethod
}
func (s *sigHead) unmarshal() error {
if err := s.protected.UnmarshalJSON(s.Protected); err != nil {
return err
}
return s.unprotected.UnmarshalJSON(s.Unprotected)
}
// New creates a JWS with the provided crypto.SigningMethods.
func New(content interface{}, methods ...crypto.SigningMethod) JWS {
sb := make([]sigHead, len(methods))
for i := range methods {
sb[i] = sigHead{
protected: jose.Protected{
"alg": methods[i].Alg(),
},
unprotected: jose.Header{},
method: methods[i],
}
}
return &jws{
payload: &payload{v: content},
sb: sb,
}
}
func (s *sigHead) assignMethod(p jose.Protected) error {
alg, ok := p.Get("alg").(string)
if !ok {
return ErrNoAlgorithm
}
sm := GetSigningMethod(alg)
if sm == nil {
return ErrNoAlgorithm
}
s.method = sm
return nil
}
type generic struct {
Payload rawBase64 `json:"payload"`
sigHead
Signatures []sigHead `json:"signatures,omitempty"`
}
// Parse parses any of the three serialized jws forms into a physical
// jws per https://tools.ietf.org/html/rfc7515#section-5.2
//
// It accepts a json.Unmarshaler in order to properly parse
// the payload. In order to keep the caller from having to do extra
// parsing of the payload, a json.Unmarshaler can be passed
// which will be then to unmarshal the payload however the caller
// wishes. Do note that if json.Unmarshal returns an error the
// original payload will be used as if no json.Unmarshaler was
// passed.
//
// Internally, Parse applies some heuristics and then calls either
// ParseGeneral, ParseFlat, or ParseCompact.
// It should only be called if, for whatever reason, you do not
// know which form the serialized JWT is in.
//
// It cannot parse a JWT.
func Parse(encoded []byte, u ...json.Unmarshaler) (JWS, error) {
// Try and unmarshal into a generic struct that'll
// hopefully hold either of the two JSON serialization
// formats.
var g generic
// Not valid JSON. Let's try compact.
if err := json.Unmarshal(encoded, &g); err != nil {
return ParseCompact(encoded, u...)
}
if g.Signatures == nil {
return g.parseFlat(u...)
}
return g.parseGeneral(u...)
}
// ParseGeneral parses a jws serialized into its "general" form per
// https://tools.ietf.org/html/rfc7515#section-7.2.1
// into a physical jws per
// https://tools.ietf.org/html/rfc7515#section-5.2
//
// For information on the json.Unmarshaler parameter, see Parse.
func ParseGeneral(encoded []byte, u ...json.Unmarshaler) (JWS, error) {
var g generic
if err := json.Unmarshal(encoded, &g); err != nil {
return nil, err
}
return g.parseGeneral(u...)
}
func (g *generic) parseGeneral(u ...json.Unmarshaler) (JWS, error) {
var p payload
if len(u) > 0 {
p.u = u[0]
}
if err := p.UnmarshalJSON(g.Payload); err != nil {
return nil, err
}
for i := range g.Signatures {
if err := g.Signatures[i].unmarshal(); err != nil {
return nil, err
}
if err := checkHeaders(jose.Header(g.Signatures[i].protected), g.Signatures[i].unprotected); err != nil {
return nil, err
}
if err := g.Signatures[i].assignMethod(g.Signatures[i].protected); err != nil {
return nil, err
}
}
g.clean = len(g.Signatures) != 0
return &jws{
payload: &p,
plcache: g.Payload,
clean: true,
sb: g.Signatures,
}, nil
}
// ParseFlat parses a jws serialized into its "flat" form per
// https://tools.ietf.org/html/rfc7515#section-7.2.2
// into a physical jws per
// https://tools.ietf.org/html/rfc7515#section-5.2
//
// For information on the json.Unmarshaler parameter, see Parse.
func ParseFlat(encoded []byte, u ...json.Unmarshaler) (JWS, error) {
var g generic
if err := json.Unmarshal(encoded, &g); err != nil {
return nil, err
}
return g.parseFlat(u...)
}
func (g *generic) parseFlat(u ...json.Unmarshaler) (JWS, error) {
var p payload
if len(u) > 0 {
p.u = u[0]
}
if err := p.UnmarshalJSON(g.Payload); err != nil {
return nil, err
}
if err := g.sigHead.unmarshal(); err != nil {
return nil, err
}
g.sigHead.clean = true
if err := checkHeaders(jose.Header(g.sigHead.protected), g.sigHead.unprotected); err != nil {
return nil, err
}
if err := g.sigHead.assignMethod(g.sigHead.protected); err != nil {
return nil, err
}
return &jws{
payload: &p,
plcache: g.Payload,
clean: true,
sb: []sigHead{g.sigHead},
}, nil
}
// ParseCompact parses a jws serialized into its "compact" form per
// https://tools.ietf.org/html/rfc7515#section-7.1
// into a physical jws per
// https://tools.ietf.org/html/rfc7515#section-5.2
//
// For information on the json.Unmarshaler parameter, see Parse.
func ParseCompact(encoded []byte, u ...json.Unmarshaler) (JWS, error) {
return parseCompact(encoded, false, u...)
}
func parseCompact(encoded []byte, jwt bool, u ...json.Unmarshaler) (*jws, error) {
// This section loosely follows
// https://tools.ietf.org/html/rfc7519#section-7.2
// because it's used to parse _both_ jws and JWTs.
parts := bytes.Split(encoded, []byte{'.'})
if len(parts) != 3 {
return nil, ErrNotCompact
}
var p jose.Protected
if err := p.UnmarshalJSON(parts[0]); err != nil {
return nil, err
}
s := sigHead{
Protected: parts[0],
protected: p,
Signature: parts[2],
clean: true,
}
if err := s.assignMethod(p); err != nil {
return nil, err
}
var pl payload
if len(u) > 0 {
pl.u = u[0]
}
j := jws{
payload: &pl,
plcache: parts[1],
sb: []sigHead{s},
isJWT: jwt,
}
if err := j.payload.UnmarshalJSON(parts[1]); err != nil {
return nil, err
}
j.clean = true
if err := j.sb[0].Signature.UnmarshalJSON(parts[2]); err != nil {
return nil, err
}
// https://tools.ietf.org/html/rfc7519#section-7.2.8
cty, ok := p.Get("cty").(string)
if ok && cty == "JWT" {
return &j, ErrHoldsJWE
}
return &j, nil
}
var (
// JWSFormKey is the form "key" which should be used inside
// ParseFromRequest if the request is a multipart.Form.
JWSFormKey = "access_token"
// MaxMemory is maximum amount of memory which should be used
// inside ParseFromRequest while parsing the multipart.Form
// if the request is a multipart.Form.
MaxMemory int64 = 10e6
)
// Format specifies which "format" the JWS is in -- Flat, General,
// or compact. Additionally, constants for JWT/Unknown are added.
type Format uint8
const (
// Unknown format.
Unknown Format = iota
// Flat format.
Flat
// General format.
General
// Compact format.
Compact
)
var parseJumpTable = [...]func([]byte, ...json.Unmarshaler) (JWS, error){
Unknown: Parse,
Flat: ParseFlat,
General: ParseGeneral,
Compact: ParseCompact,
1<<8 - 1: Parse, // Max uint8.
}
func init() {
for i := range parseJumpTable {
if parseJumpTable[i] == nil {
parseJumpTable[i] = Parse
}
}
}
func fromHeader(req *http.Request) ([]byte, bool) {
if ah := req.Header.Get("Authorization"); len(ah) > 7 && strings.EqualFold(ah[0:7], "BEARER ") {
return []byte(ah[7:]), true
}
return nil, false
}
func fromForm(req *http.Request) ([]byte, bool) {
if err := req.ParseMultipartForm(MaxMemory); err != nil {
return nil, false
}
if tokStr := req.Form.Get(JWSFormKey); tokStr != "" {
return []byte(tokStr), true
}
return nil, false
}
// ParseFromHeader tries to find the JWS in an http.Request header.
func ParseFromHeader(req *http.Request, format Format, u ...json.Unmarshaler) (JWS, error) {
if b, ok := fromHeader(req); ok {
return parseJumpTable[format](b, u...)
}
return nil, ErrNoTokenInRequest
}
// ParseFromForm tries to find the JWS in an http.Request form request.
func ParseFromForm(req *http.Request, format Format, u ...json.Unmarshaler) (JWS, error) {
if b, ok := fromForm(req); ok {
return parseJumpTable[format](b, u...)
}
return nil, ErrNoTokenInRequest
}
// ParseFromRequest tries to find the JWS in an http.Request.
// This method will call ParseMultipartForm if there's no token in the header.
func ParseFromRequest(req *http.Request, format Format, u ...json.Unmarshaler) (JWS, error) {
token, err := ParseFromHeader(req, format, u...)
if err == nil {
return token, nil
}
token, err = ParseFromForm(req, format, u...)
if err == nil {
return token, nil
}
return nil, err
}
// IgnoreDupes should be set to true if the internal duplicate header key check
// should ignore duplicate Header keys instead of reporting an error when
// duplicate Header keys are found.
//
// Note:
// Duplicate Header keys are defined in
// https://tools.ietf.org/html/rfc7515#section-5.2
// meaning keys that both the protected and unprotected
// Headers possess.
var IgnoreDupes bool
// checkHeaders returns an error per the constraints described in
// IgnoreDupes' comment.
func checkHeaders(a, b jose.Header) error {
if len(a)+len(b) == 0 {
return ErrTwoEmptyHeaders
}
for key := range a {
if b.Has(key) && !IgnoreDupes {
return ErrDuplicateHeaderParameter
}
}
return nil
}
var _ JWS = (*jws)(nil)