| package jws |
| |
| import ( |
| "fmt" |
| |
| "github.com/SermoDigital/jose/crypto" |
| ) |
| |
| // VerifyCallback is 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. |
| type VerifyCallback func(JWS) ([]interface{}, 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. |
| func (j *jws) VerifyCallback(fn VerifyCallback, methods []crypto.SigningMethod, o *SigningOpts) error { |
| keys, err := fn(j) |
| if err != nil { |
| return err |
| } |
| return j.VerifyMulti(keys, methods, o) |
| } |
| |
| // IsMultiError returns true if the given error is type *MultiError. |
| func IsMultiError(err error) bool { |
| _, ok := err.(*MultiError) |
| return ok |
| } |
| |
| // MultiError is a slice of errors. |
| type MultiError []error |
| |
| // Errors implements the error interface. |
| func (m *MultiError) Error() string { |
| var s string |
| var n int |
| for _, err := range *m { |
| if err != nil { |
| if n == 0 { |
| s = err.Error() |
| } |
| n++ |
| } |
| } |
| switch n { |
| case 0: |
| return "" |
| case 1: |
| return s |
| case 2: |
| return s + " and 1 other error" |
| } |
| return fmt.Sprintf("%s (and %d other errors)", s, n-1) |
| } |
| |
| // Any means any of the JWS signatures need to verify. |
| // Refer to verifyMulti for more information. |
| const Any int = 0 |
| |
| // VerifyMulti verifies 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. |
| func (j *jws) VerifyMulti(keys []interface{}, methods []crypto.SigningMethod, o *SigningOpts) error { |
| |
| // Catch a simple mistake. Parameter o is irrelevant in this scenario. |
| if len(keys) == 1 && |
| len(methods) == 1 && |
| len(j.sb) == 1 { |
| return j.Verify(keys[0], methods[0]) |
| } |
| |
| if len(j.sb) != len(methods) { |
| return ErrNotEnoughMethods |
| } |
| |
| if len(keys) < 1 || |
| len(keys) > 1 && len(keys) != len(j.sb) { |
| return ErrNotEnoughKeys |
| } |
| |
| // TODO do this better. |
| if len(keys) == 1 { |
| k := keys[0] |
| keys = make([]interface{}, len(methods)) |
| for i := range keys { |
| keys[i] = k |
| } |
| } |
| |
| var o2 SigningOpts |
| if o == nil { |
| o = new(SigningOpts) |
| } |
| |
| var m MultiError |
| for i := range j.sb { |
| err := j.sb[i].verify(j.plcache, keys[i], methods[i]) |
| if err != nil { |
| m = append(m, err) |
| } else { |
| o2.Inc() |
| if o.Needs(i) { |
| o.ptr++ |
| o2.Append(i) |
| } |
| } |
| } |
| |
| err := o.Validate(&o2) |
| if err != nil { |
| m = append(m, err) |
| } |
| if len(m) == 0 { |
| return nil |
| } |
| return &m |
| } |
| |
| // SigningOpts is a struct which holds options for validating |
| // JWS signatures. |
| // Number represents the cumulative which signatures need to verify |
| // in order for the JWS to be considered valid. |
| // Leave 'Number' empty or set it to the constant 'Any' if any number of |
| // valid signatures (greater than one) should verify the JWS. |
| // |
| // Use the indices of the signatures that need to verify in order |
| // for the JWS to be considered valid if specific signatures need |
| // to verify in order for the JWS to be considered valid. |
| // |
| // Note: |
| // The JWS spec requires *at least* one |
| // signature to verify in order for the JWS to be considered valid. |
| type SigningOpts struct { |
| // Minimum of signatures which need to verify. |
| Number int |
| |
| // Indices of specific signatures which need to verify. |
| Indices []int |
| ptr int |
| |
| _ struct{} |
| } |
| |
| // Append appends x to s' Indices member. |
| func (s *SigningOpts) Append(x int) { |
| s.Indices = append(s.Indices, x) |
| } |
| |
| // Needs returns true if x resides inside s' Indices member |
| // for the given index. It's used to match two SigningOpts Indices members. |
| func (s *SigningOpts) Needs(x int) bool { |
| return s.ptr < len(s.Indices) && s.Indices[s.ptr] == x |
| } |
| |
| // Inc increments s' Number member by one. |
| func (s *SigningOpts) Inc() { s.Number++ } |
| |
| // Validate returns any errors found while validating the |
| // provided SigningOpts. The receiver validates |have|. |
| // It'll return an error if the passed SigningOpts' Number member is less |
| // than s' or if the passed SigningOpts' Indices slice isn't equal to s'. |
| func (s *SigningOpts) Validate(have *SigningOpts) error { |
| if have.Number < s.Number || |
| (s.Indices != nil && |
| !eq(s.Indices, have.Indices)) { |
| return ErrNotEnoughValidSignatures |
| } |
| return nil |
| } |
| |
| func eq(a, b []int) bool { |
| if len(a) != len(b) { |
| return false |
| } |
| for i := range a { |
| if a[i] != b[i] { |
| return false |
| } |
| } |
| return true |
| } |
| |
| // Verify verifies the current JWS as-is. Refer to verifyMulti |
| // for more information. |
| func (j *jws) Verify(key interface{}, method crypto.SigningMethod) error { |
| if len(j.sb) < 1 { |
| return ErrCannotValidate |
| } |
| return j.sb[0].verify(j.plcache, key, method) |
| } |
| |
| func (s *sigHead) verify(pl []byte, key interface{}, method crypto.SigningMethod) error { |
| if s.method.Alg() != method.Alg() || s.method.Hasher() != method.Hasher() { |
| return ErrMismatchedAlgorithms |
| } |
| return method.Verify(format(s.Protected, pl), s.Signature, key) |
| } |