mirror of https://github.com/hashicorp/consul
204 lines
5.1 KiB
Go
204 lines
5.1 KiB
Go
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)
|
|
}
|