blob: 4147d3726e624b77447b6a8f00fe8d5246bd70ca [file] [log] [blame]
package jws
import (
"bytes"
"encoding/json"
"sort"
"github.com/SermoDigital/jose"
)
// JWS represents a specific JWS.
type JWS struct {
payload *payload
plcache rawBase64
clean bool
sb []sigHead
}
// 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 Signature `json:"signature"`
protected jose.Protected `json:"-"`
unprotected jose.Header `json:"-"`
clean bool `json:"-"`
method SigningMethod
}
func (s *sigHead) unmarshal() error {
if err := s.protected.UnmarshalJSON(s.Protected); err != nil {
return err
}
if err := s.unprotected.UnmarshalJSON(s.Unprotected); err != nil {
return err
}
return nil
}
// New creates a new JWS with the provided SigningMethods.
func New(content interface{}, methods ...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.
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.s
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 = true
}
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) {
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: p,
clean: true,
}
if err := s.assignMethod(p); err != nil {
return nil, err
}
j := JWS{
payload: &payload{},
sb: []sigHead{s},
}
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
}
return &j, nil
}
// 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
}
const Any int = -1
// ValidateMulti validates the current JWS as-is. Since it's meant to be
// called after parsing a stream of bytes into a JWS, it doesn't do any
// internal parsing like the Sign, Flat, Compact, or General methods do.
// idx represents which signatures need to validate
// in order for the JWS to be considered valid.
// Use the constant `Any` (-1) if _any_ should validate the JWS. Otherwise,
// use the indexes of the signatures that need to validate in order
// for the JWS to be considered valid.
// Note: if idx is omitted it defaults to requiring _all_
// signatures validate, and the JWS spec required _at least_ one
// signature to validate in order for the JWS to be considered
// valid.
func (j *JWS) ValidateMulti(keys []interface{}, methods []SigningMethod, idx ...int) error {
if len(j.sb) != len(methods) {
return ErrNotEnoughMethods
}
if len(keys) < 1 ||
len(keys) > 1 && len(keys) != len(j.sb) {
return ErrNotEnoughKeys
}
if len(keys) == 1 {
k := keys[0]
keys = make([]interface{}, len(methods))
for i := range keys {
keys[i] = k
}
}
any := len(idx) == 1 && idx[0] == Any
if !any {
sort.Ints(idx)
}
rp := 0
for i := range j.sb {
if j.sb[i].validate(j.plcache, keys[i], methods[i]) == nil &&
any || (rp < len(idx) && idx[rp] == i) {
rp++
}
}
if rp < len(idx) {
return ErrDidNotValidate
}
return nil
}
// Validate validates the current JWS as-is. Refer to ValidateMulti
// for more information.
func (j *JWS) Validate(key interface{}, method SigningMethod) error {
if len(j.sb) < 1 {
return ErrCannotValidate
}
return j.sb[0].validate(j.plcache, key, method)
}
func (s *sigHead) validate(pl []byte, key interface{}, method SigningMethod) error {
if s.method != method {
return ErrMismatchedAlgorithms
}
return method.Verify(format(s.Protected, pl), s.Signature, key)
}