| package jws |
| |
| import ( |
| "bytes" |
| "encoding/json" |
| "net/http" |
| "strings" |
| |
| "github.com/SermoDigital/jose" |
| "github.com/SermoDigital/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) |