Signed-off-by: hongming <talonwan@yunify.com>
This commit is contained in:
hongming
2020-03-19 22:44:05 +08:00
parent 23f6be88c6
commit 9769357005
332 changed files with 69808 additions and 4129 deletions

View File

@@ -0,0 +1,154 @@
package jws
import (
"github.com/pkg/errors"
"github.com/open-policy-agent/opa/topdown/internal/jwx/jwa"
)
// Constants for JWS Common parameters
const (
AlgorithmKey = "alg"
ContentTypeKey = "cty"
CriticalKey = "crit"
JWKKey = "jwk"
JWKSetURLKey = "jku"
KeyIDKey = "kid"
PrivateParamsKey = "privateParams"
TypeKey = "typ"
)
// Headers provides a common interface for common header parameters
type Headers interface {
Get(string) (interface{}, bool)
Set(string, interface{}) error
GetAlgorithm() jwa.SignatureAlgorithm
}
// StandardHeaders contains JWS common parameters.
type StandardHeaders struct {
Algorithm jwa.SignatureAlgorithm `json:"alg,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.1
ContentType string `json:"cty,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.10
Critical []string `json:"crit,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.11
JWK string `json:"jwk,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.3
JWKSetURL string `json:"jku,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.2
KeyID string `json:"kid,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.4
PrivateParams map[string]interface{} `json:"privateParams,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.9
Type string `json:"typ,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.9
}
// GetAlgorithm returns algorithm
func (h *StandardHeaders) GetAlgorithm() jwa.SignatureAlgorithm {
return h.Algorithm
}
// Get is a general getter function for StandardHeaders structure
func (h *StandardHeaders) Get(name string) (interface{}, bool) {
switch name {
case AlgorithmKey:
v := h.Algorithm
if v == "" {
return nil, false
}
return v, true
case ContentTypeKey:
v := h.ContentType
if v == "" {
return nil, false
}
return v, true
case CriticalKey:
v := h.Critical
if len(v) == 0 {
return nil, false
}
return v, true
case JWKKey:
v := h.JWK
if v == "" {
return nil, false
}
return v, true
case JWKSetURLKey:
v := h.JWKSetURL
if v == "" {
return nil, false
}
return v, true
case KeyIDKey:
v := h.KeyID
if v == "" {
return nil, false
}
return v, true
case PrivateParamsKey:
v := h.PrivateParams
if len(v) == 0 {
return nil, false
}
return v, true
case TypeKey:
v := h.Type
if v == "" {
return nil, false
}
return v, true
default:
return nil, false
}
}
// Set is a general setter function for StandardHeaders structure
func (h *StandardHeaders) Set(name string, value interface{}) error {
switch name {
case AlgorithmKey:
if err := h.Algorithm.Accept(value); err != nil {
return errors.Wrapf(err, `invalid value for %s key`, AlgorithmKey)
}
return nil
case ContentTypeKey:
if v, ok := value.(string); ok {
h.ContentType = v
return nil
}
return errors.Errorf(`invalid value for %s key: %T`, ContentTypeKey, value)
case CriticalKey:
if v, ok := value.([]string); ok {
h.Critical = v
return nil
}
return errors.Errorf(`invalid value for %s key: %T`, CriticalKey, value)
case JWKKey:
if v, ok := value.(string); ok {
h.JWK = v
return nil
}
return errors.Errorf(`invalid value for %s key: %T`, JWKKey, value)
case JWKSetURLKey:
if v, ok := value.(string); ok {
h.JWKSetURL = v
return nil
}
return errors.Errorf(`invalid value for %s key: %T`, JWKSetURLKey, value)
case KeyIDKey:
if v, ok := value.(string); ok {
h.KeyID = v
return nil
}
return errors.Errorf(`invalid value for %s key: %T`, KeyIDKey, value)
case PrivateParamsKey:
if v, ok := value.(map[string]interface{}); ok {
h.PrivateParams = v
return nil
}
return errors.Errorf(`invalid value for %s key: %T`, PrivateParamsKey, value)
case TypeKey:
if v, ok := value.(string); ok {
h.Type = v
return nil
}
return errors.Errorf(`invalid value for %s key: %T`, TypeKey, value)
default:
return errors.Errorf(`invalid key: %s`, name)
}
}

View File

@@ -0,0 +1,22 @@
package jws
// Message represents a full JWS encoded message. Flattened serialization
// is not supported as a struct, but rather it's represented as a
// Message struct with only one `Signature` element.
//
// Do not expect to use the Message object to verify or construct a
// signed payloads with. You should only use this when you want to actually
// want to programmatically view the contents for the full JWS Payload.
//
// To sign and verify, use the appropriate `SignWithOption()` nad `Verify()` functions
type Message struct {
Payload []byte `json:"payload"`
Signatures []*Signature `json:"signatures,omitempty"`
}
// Signature represents the headers and signature of a JWS message
type Signature struct {
Headers Headers `json:"header,omitempty"` // Unprotected Headers
Protected Headers `json:"Protected,omitempty"` // Protected Headers
Signature []byte `json:"signature,omitempty"` // GetSignature
}

View File

@@ -0,0 +1,210 @@
// Package jws implements the digital Signature on JSON based data
// structures as described in https://tools.ietf.org/html/rfc7515
//
// If you do not care about the details, the only things that you
// would need to use are the following functions:
//
// jws.SignWithOption(Payload, algorithm, key)
// jws.Verify(encodedjws, algorithm, key)
//
// To sign, simply use `jws.SignWithOption`. `Payload` is a []byte buffer that
// contains whatever data you want to sign. `alg` is one of the
// jwa.SignatureAlgorithm constants from package jwa. For RSA and
// ECDSA family of algorithms, you will need to prepare a private key.
// For HMAC family, you just need a []byte value. The `jws.SignWithOption`
// function will return the encoded JWS message on success.
//
// To verify, use `jws.Verify`. It will parse the `encodedjws` buffer
// and verify the result using `algorithm` and `key`. Upon successful
// verification, the original Payload is returned, so you can work on it.
package jws
import (
"bytes"
"encoding/base64"
"encoding/json"
"strings"
"github.com/open-policy-agent/opa/topdown/internal/jwx/jwa"
"github.com/open-policy-agent/opa/topdown/internal/jwx/jwk"
"github.com/open-policy-agent/opa/topdown/internal/jwx/jws/sign"
"github.com/open-policy-agent/opa/topdown/internal/jwx/jws/verify"
"github.com/pkg/errors"
)
// SignLiteral generates a Signature for the given Payload and Headers, and serializes
// it in compact serialization format. In this format you may NOT use
// multiple signers.
//
func SignLiteral(payload []byte, alg jwa.SignatureAlgorithm, key interface{}, hdrBuf []byte) ([]byte, error) {
encodedHdr := base64.RawURLEncoding.EncodeToString(hdrBuf)
encodedPayload := base64.RawURLEncoding.EncodeToString(payload)
signingInput := strings.Join(
[]string{
encodedHdr,
encodedPayload,
}, ".",
)
signer, err := sign.New(alg)
if err != nil {
return nil, errors.Wrap(err, `failed to create signer`)
}
signature, err := signer.Sign([]byte(signingInput), key)
if err != nil {
return nil, errors.Wrap(err, `failed to sign Payload`)
}
encodedSignature := base64.RawURLEncoding.EncodeToString(signature)
compactSerialization := strings.Join(
[]string{
signingInput,
encodedSignature,
}, ".",
)
return []byte(compactSerialization), nil
}
// SignWithOption generates a Signature for the given Payload, and serializes
// it in compact serialization format. In this format you may NOT use
// multiple signers.
//
// If you would like to pass custom Headers, use the WithHeaders option.
func SignWithOption(payload []byte, alg jwa.SignatureAlgorithm, key interface{}) ([]byte, error) {
var headers Headers = &StandardHeaders{}
err := headers.Set(AlgorithmKey, alg)
if err != nil {
return nil, errors.Wrap(err, "Failed to set alg value")
}
hdrBuf, err := json.Marshal(headers)
if err != nil {
return nil, errors.Wrap(err, `failed to marshal Headers`)
}
return SignLiteral(payload, alg, key, hdrBuf)
}
// Verify checks if the given JWS message is verifiable using `alg` and `key`.
// If the verification is successful, `err` is nil, and the content of the
// Payload that was signed is returned. If you need more fine-grained
// control of the verification process, manually call `Parse`, generate a
// verifier, and call `Verify` on the parsed JWS message object.
func Verify(buf []byte, alg jwa.SignatureAlgorithm, key interface{}) (ret []byte, err error) {
verifier, err := verify.New(alg)
if err != nil {
return nil, errors.Wrap(err, "failed to create verifier")
}
buf = bytes.TrimSpace(buf)
if len(buf) == 0 {
return nil, errors.New(`attempt to verify empty buffer`)
}
parts, err := SplitCompact(string(buf[:]))
if err != nil {
return nil, errors.Wrap(err, `failed extract from compact serialization format`)
}
signingInput := strings.Join(
[]string{
parts[0],
parts[1],
}, ".",
)
decodedSignature, err := base64.RawURLEncoding.DecodeString(parts[2])
if err != nil {
return nil, errors.Wrap(err, "Failed to decode signature")
}
if err := verifier.Verify([]byte(signingInput), decodedSignature, key); err != nil {
return nil, errors.Wrap(err, "Failed to verify message")
}
if decodedPayload, err := base64.RawURLEncoding.DecodeString(parts[1]); err == nil {
return decodedPayload, nil
}
return nil, errors.Wrap(err, "Failed to decode Payload")
}
// VerifyWithJWK verifies the JWS message using the specified JWK
func VerifyWithJWK(buf []byte, key jwk.Key) (payload []byte, err error) {
keyVal, err := key.Materialize()
if err != nil {
return nil, errors.Wrap(err, "Failed to materialize key")
}
return Verify(buf, key.GetAlgorithm(), keyVal)
}
// VerifyWithJWKSet verifies the JWS message using JWK key set.
// By default it will only pick up keys that have the "use" key
// set to either "sig" or "enc", but you can override it by
// providing a keyaccept function.
func VerifyWithJWKSet(buf []byte, keyset *jwk.Set) (payload []byte, err error) {
for _, key := range keyset.Keys {
payload, err := VerifyWithJWK(buf, key)
if err == nil {
return payload, nil
}
}
return nil, errors.New("failed to verify with any of the keys")
}
// ParseByte parses a JWS value serialized via compact serialization and provided as []byte.
func ParseByte(jwsCompact []byte) (m *Message, err error) {
return parseCompact(string(jwsCompact[:]))
}
// ParseString parses a JWS value serialized via compact serialization and provided as string.
func ParseString(s string) (*Message, error) {
return parseCompact(s)
}
// SplitCompact splits a JWT and returns its three parts
// separately: Protected Headers, Payload and Signature.
func SplitCompact(jwsCompact string) ([]string, error) {
parts := strings.Split(jwsCompact, ".")
if len(parts) < 3 {
return nil, errors.New("Failed to split compact serialization")
}
return parts, nil
}
// parseCompact parses a JWS value serialized via compact serialization.
func parseCompact(str string) (m *Message, err error) {
var decodedHeader, decodedPayload, decodedSignature []byte
parts, err := SplitCompact(str)
if err != nil {
return nil, errors.Wrap(err, `invalid compact serialization format`)
}
if decodedHeader, err = base64.RawURLEncoding.DecodeString(parts[0]); err != nil {
return nil, errors.Wrap(err, `failed to decode Headers`)
}
var hdr StandardHeaders
if err := json.Unmarshal(decodedHeader, &hdr); err != nil {
return nil, errors.Wrap(err, `failed to parse JOSE Headers`)
}
if decodedPayload, err = base64.RawURLEncoding.DecodeString(parts[1]); err != nil {
return nil, errors.Wrap(err, `failed to decode Payload`)
}
if len(parts) > 2 {
if decodedSignature, err = base64.RawURLEncoding.DecodeString(parts[2]); err != nil {
return nil, errors.Wrap(err, `failed to decode Signature`)
}
}
var msg Message
msg.Payload = decodedPayload
msg.Signatures = append(msg.Signatures, &Signature{
Protected: &hdr,
Signature: decodedSignature,
})
return &msg, nil
}

View File

@@ -0,0 +1,26 @@
package jws
// PublicHeaders returns the public headers in a JWS
func (s Signature) PublicHeaders() Headers {
return s.Headers
}
// ProtectedHeaders returns the protected headers in a JWS
func (s Signature) ProtectedHeaders() Headers {
return s.Protected
}
// GetSignature returns the signature in a JWS
func (s Signature) GetSignature() []byte {
return s.Signature
}
// GetPayload returns the payload in a JWS
func (m Message) GetPayload() []byte {
return m.Payload
}
// GetSignatures returns the all signatures in a JWS
func (m Message) GetSignatures() []*Signature {
return m.Signatures
}

View File

@@ -0,0 +1,84 @@
package sign
import (
"crypto"
"crypto/ecdsa"
"crypto/rand"
"github.com/open-policy-agent/opa/topdown/internal/jwx/jwa"
"github.com/pkg/errors"
)
var ecdsaSignFuncs = map[jwa.SignatureAlgorithm]ecdsaSignFunc{}
func init() {
algs := map[jwa.SignatureAlgorithm]crypto.Hash{
jwa.ES256: crypto.SHA256,
jwa.ES384: crypto.SHA384,
jwa.ES512: crypto.SHA512,
}
for alg, h := range algs {
ecdsaSignFuncs[alg] = makeECDSASignFunc(h)
}
}
func makeECDSASignFunc(hash crypto.Hash) ecdsaSignFunc {
return ecdsaSignFunc(func(payload []byte, key *ecdsa.PrivateKey) ([]byte, error) {
curveBits := key.Curve.Params().BitSize
keyBytes := curveBits / 8
// Curve bits do not need to be a multiple of 8.
if curveBits%8 > 0 {
keyBytes++
}
h := hash.New()
h.Write(payload)
r, s, err := ecdsa.Sign(rand.Reader, key, h.Sum(nil))
if err != nil {
return nil, errors.Wrap(err, "failed to sign payload using ecdsa")
}
rBytes := r.Bytes()
rBytesPadded := make([]byte, keyBytes)
copy(rBytesPadded[keyBytes-len(rBytes):], rBytes)
sBytes := s.Bytes()
sBytesPadded := make([]byte, keyBytes)
copy(sBytesPadded[keyBytes-len(sBytes):], sBytes)
out := append(rBytesPadded, sBytesPadded...)
return out, nil
})
}
func newECDSA(alg jwa.SignatureAlgorithm) (*ECDSASigner, error) {
signfn, ok := ecdsaSignFuncs[alg]
if !ok {
return nil, errors.Errorf(`unsupported algorithm while trying to create ECDSA signer: %s`, alg)
}
return &ECDSASigner{
alg: alg,
sign: signfn,
}, nil
}
// Algorithm returns the signer algorithm
func (s ECDSASigner) Algorithm() jwa.SignatureAlgorithm {
return s.alg
}
// Sign signs payload with a ECDSA private key
func (s ECDSASigner) Sign(payload []byte, key interface{}) ([]byte, error) {
if key == nil {
return nil, errors.New(`missing private key while signing payload`)
}
privateKey, ok := key.(*ecdsa.PrivateKey)
if !ok {
return nil, errors.Errorf(`invalid key type %T. *ecdsa.PrivateKey is required`, key)
}
return s.sign(payload, privateKey)
}

View File

@@ -0,0 +1,66 @@
package sign
import (
"crypto/hmac"
"crypto/sha256"
"crypto/sha512"
"hash"
"github.com/open-policy-agent/opa/topdown/internal/jwx/jwa"
"github.com/pkg/errors"
)
var hmacSignFuncs = map[jwa.SignatureAlgorithm]hmacSignFunc{}
func init() {
algs := map[jwa.SignatureAlgorithm]func() hash.Hash{
jwa.HS256: sha256.New,
jwa.HS384: sha512.New384,
jwa.HS512: sha512.New,
}
for alg, h := range algs {
hmacSignFuncs[alg] = makeHMACSignFunc(h)
}
}
func newHMAC(alg jwa.SignatureAlgorithm) (*HMACSigner, error) {
signer, ok := hmacSignFuncs[alg]
if !ok {
return nil, errors.Errorf(`unsupported algorithm while trying to create HMAC signer: %s`, alg)
}
return &HMACSigner{
alg: alg,
sign: signer,
}, nil
}
func makeHMACSignFunc(hfunc func() hash.Hash) hmacSignFunc {
return hmacSignFunc(func(payload []byte, key []byte) ([]byte, error) {
h := hmac.New(hfunc, key)
h.Write(payload)
return h.Sum(nil), nil
})
}
// Algorithm returns the signer algorithm
func (s HMACSigner) Algorithm() jwa.SignatureAlgorithm {
return s.alg
}
// Sign signs payload with a Symmetric key
func (s HMACSigner) Sign(payload []byte, key interface{}) ([]byte, error) {
hmackey, ok := key.([]byte)
if !ok {
return nil, errors.Errorf(`invalid key type %T. []byte is required`, key)
}
if len(hmackey) == 0 {
return nil, errors.New(`missing key while signing payload`)
}
return s.sign(payload, hmackey)
}

View File

@@ -0,0 +1,45 @@
package sign
import (
"crypto/ecdsa"
"crypto/rsa"
"github.com/open-policy-agent/opa/topdown/internal/jwx/jwa"
)
// Signer provides a common interface for supported alg signing methods
type Signer interface {
// Sign creates a signature for the given `payload`.
// `key` is the key used for signing the payload, and is usually
// the private key type associated with the signature method. For example,
// for `jwa.RSXXX` and `jwa.PSXXX` types, you need to pass the
// `*"crypto/rsa".PrivateKey` type.
// Check the documentation for each signer for details
Sign(payload []byte, key interface{}) ([]byte, error)
Algorithm() jwa.SignatureAlgorithm
}
type rsaSignFunc func([]byte, *rsa.PrivateKey) ([]byte, error)
// RSASigner uses crypto/rsa to sign the payloads.
type RSASigner struct {
alg jwa.SignatureAlgorithm
sign rsaSignFunc
}
type ecdsaSignFunc func([]byte, *ecdsa.PrivateKey) ([]byte, error)
// ECDSASigner uses crypto/ecdsa to sign the payloads.
type ECDSASigner struct {
alg jwa.SignatureAlgorithm
sign ecdsaSignFunc
}
type hmacSignFunc func([]byte, []byte) ([]byte, error)
// HMACSigner uses crypto/hmac to sign the payloads.
type HMACSigner struct {
alg jwa.SignatureAlgorithm
sign hmacSignFunc
}

View File

@@ -0,0 +1,97 @@
package sign
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"github.com/open-policy-agent/opa/topdown/internal/jwx/jwa"
"github.com/pkg/errors"
)
var rsaSignFuncs = map[jwa.SignatureAlgorithm]rsaSignFunc{}
func init() {
algs := map[jwa.SignatureAlgorithm]struct {
Hash crypto.Hash
SignFunc func(crypto.Hash) rsaSignFunc
}{
jwa.RS256: {
Hash: crypto.SHA256,
SignFunc: makeSignPKCS1v15,
},
jwa.RS384: {
Hash: crypto.SHA384,
SignFunc: makeSignPKCS1v15,
},
jwa.RS512: {
Hash: crypto.SHA512,
SignFunc: makeSignPKCS1v15,
},
jwa.PS256: {
Hash: crypto.SHA256,
SignFunc: makeSignPSS,
},
jwa.PS384: {
Hash: crypto.SHA384,
SignFunc: makeSignPSS,
},
jwa.PS512: {
Hash: crypto.SHA512,
SignFunc: makeSignPSS,
},
}
for alg, item := range algs {
rsaSignFuncs[alg] = item.SignFunc(item.Hash)
}
}
func makeSignPKCS1v15(hash crypto.Hash) rsaSignFunc {
return rsaSignFunc(func(payload []byte, key *rsa.PrivateKey) ([]byte, error) {
h := hash.New()
h.Write(payload)
return rsa.SignPKCS1v15(rand.Reader, key, hash, h.Sum(nil))
})
}
func makeSignPSS(hash crypto.Hash) rsaSignFunc {
return rsaSignFunc(func(payload []byte, key *rsa.PrivateKey) ([]byte, error) {
h := hash.New()
h.Write(payload)
return rsa.SignPSS(rand.Reader, key, hash, h.Sum(nil), &rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthAuto,
})
})
}
func newRSA(alg jwa.SignatureAlgorithm) (*RSASigner, error) {
signfn, ok := rsaSignFuncs[alg]
if !ok {
return nil, errors.Errorf(`unsupported algorithm while trying to create RSA signer: %s`, alg)
}
return &RSASigner{
alg: alg,
sign: signfn,
}, nil
}
// Algorithm returns the signer algorithm
func (s RSASigner) Algorithm() jwa.SignatureAlgorithm {
return s.alg
}
// Sign creates a signature using crypto/rsa. key must be a non-nil instance of
// `*"crypto/rsa".PrivateKey`.
func (s RSASigner) Sign(payload []byte, key interface{}) ([]byte, error) {
if key == nil {
return nil, errors.New(`missing private key while signing payload`)
}
rsakey, ok := key.(*rsa.PrivateKey)
if !ok {
return nil, errors.Errorf(`invalid key type %T. *rsa.PrivateKey is required`, key)
}
return s.sign(payload, rsakey)
}

View File

@@ -0,0 +1,21 @@
package sign
import (
"github.com/pkg/errors"
"github.com/open-policy-agent/opa/topdown/internal/jwx/jwa"
)
// New creates a signer that signs payloads using the given signature algorithm.
func New(alg jwa.SignatureAlgorithm) (Signer, error) {
switch alg {
case jwa.RS256, jwa.RS384, jwa.RS512, jwa.PS256, jwa.PS384, jwa.PS512:
return newRSA(alg)
case jwa.ES256, jwa.ES384, jwa.ES512:
return newECDSA(alg)
case jwa.HS256, jwa.HS384, jwa.HS512:
return newHMAC(alg)
default:
return nil, errors.Errorf(`unsupported signature algorithm %s`, alg)
}
}

View File

@@ -0,0 +1,67 @@
package verify
import (
"crypto"
"crypto/ecdsa"
"math/big"
"github.com/pkg/errors"
"github.com/open-policy-agent/opa/topdown/internal/jwx/jwa"
)
var ecdsaVerifyFuncs = map[jwa.SignatureAlgorithm]ecdsaVerifyFunc{}
func init() {
algs := map[jwa.SignatureAlgorithm]crypto.Hash{
jwa.ES256: crypto.SHA256,
jwa.ES384: crypto.SHA384,
jwa.ES512: crypto.SHA512,
}
for alg, h := range algs {
ecdsaVerifyFuncs[alg] = makeECDSAVerifyFunc(h)
}
}
func makeECDSAVerifyFunc(hash crypto.Hash) ecdsaVerifyFunc {
return ecdsaVerifyFunc(func(payload []byte, signature []byte, key *ecdsa.PublicKey) error {
r, s := &big.Int{}, &big.Int{}
n := len(signature) / 2
r.SetBytes(signature[:n])
s.SetBytes(signature[n:])
h := hash.New()
h.Write(payload)
if !ecdsa.Verify(key, h.Sum(nil), r, s) {
return errors.New(`failed to verify signature using ecdsa`)
}
return nil
})
}
func newECDSA(alg jwa.SignatureAlgorithm) (*ECDSAVerifier, error) {
verifyfn, ok := ecdsaVerifyFuncs[alg]
if !ok {
return nil, errors.Errorf(`unsupported algorithm while trying to create ECDSA verifier: %s`, alg)
}
return &ECDSAVerifier{
verify: verifyfn,
}, nil
}
// Verify checks whether the signature for a given input and key is correct
func (v ECDSAVerifier) Verify(payload []byte, signature []byte, key interface{}) error {
if key == nil {
return errors.New(`missing public key while verifying payload`)
}
ecdsakey, ok := key.(*ecdsa.PublicKey)
if !ok {
return errors.Errorf(`invalid key type %T. *ecdsa.PublicKey is required`, key)
}
return v.verify(payload, signature, ecdsakey)
}

View File

@@ -0,0 +1,33 @@
package verify
import (
"crypto/hmac"
"github.com/pkg/errors"
"github.com/open-policy-agent/opa/topdown/internal/jwx/jwa"
"github.com/open-policy-agent/opa/topdown/internal/jwx/jws/sign"
)
func newHMAC(alg jwa.SignatureAlgorithm) (*HMACVerifier, error) {
s, err := sign.New(alg)
if err != nil {
return nil, errors.Wrap(err, `failed to generate HMAC signer`)
}
return &HMACVerifier{signer: s}, nil
}
// Verify checks whether the signature for a given input and key is correct
func (v HMACVerifier) Verify(signingInput, signature []byte, key interface{}) (err error) {
expected, err := v.signer.Sign(signingInput, key)
if err != nil {
return errors.Wrap(err, `failed to generated signature`)
}
if !hmac.Equal(signature, expected) {
return errors.New(`failed to match hmac signature`)
}
return nil
}

View File

@@ -0,0 +1,39 @@
package verify
import (
"crypto/ecdsa"
"crypto/rsa"
"github.com/open-policy-agent/opa/topdown/internal/jwx/jws/sign"
)
// Verifier provides a common interface for supported alg verification methods
type Verifier interface {
// Verify checks whether the payload and signature are valid for
// the given key.
// `key` is the key used for verifying the payload, and is usually
// the public key associated with the signature method. For example,
// for `jwa.RSXXX` and `jwa.PSXXX` types, you need to pass the
// `*"crypto/rsa".PublicKey` type.
// Check the documentation for each verifier for details
Verify(payload []byte, signature []byte, key interface{}) error
}
type rsaVerifyFunc func([]byte, []byte, *rsa.PublicKey) error
// RSAVerifier implements the Verifier interface
type RSAVerifier struct {
verify rsaVerifyFunc
}
type ecdsaVerifyFunc func([]byte, []byte, *ecdsa.PublicKey) error
// ECDSAVerifier implements the Verifier interface
type ECDSAVerifier struct {
verify ecdsaVerifyFunc
}
// HMACVerifier implements the Verifier interface
type HMACVerifier struct {
signer sign.Signer
}

View File

@@ -0,0 +1,88 @@
package verify
import (
"crypto"
"crypto/rsa"
"github.com/open-policy-agent/opa/topdown/internal/jwx/jwa"
"github.com/pkg/errors"
)
var rsaVerifyFuncs = map[jwa.SignatureAlgorithm]rsaVerifyFunc{}
func init() {
algs := map[jwa.SignatureAlgorithm]struct {
Hash crypto.Hash
VerifyFunc func(crypto.Hash) rsaVerifyFunc
}{
jwa.RS256: {
Hash: crypto.SHA256,
VerifyFunc: makeVerifyPKCS1v15,
},
jwa.RS384: {
Hash: crypto.SHA384,
VerifyFunc: makeVerifyPKCS1v15,
},
jwa.RS512: {
Hash: crypto.SHA512,
VerifyFunc: makeVerifyPKCS1v15,
},
jwa.PS256: {
Hash: crypto.SHA256,
VerifyFunc: makeVerifyPSS,
},
jwa.PS384: {
Hash: crypto.SHA384,
VerifyFunc: makeVerifyPSS,
},
jwa.PS512: {
Hash: crypto.SHA512,
VerifyFunc: makeVerifyPSS,
},
}
for alg, item := range algs {
rsaVerifyFuncs[alg] = item.VerifyFunc(item.Hash)
}
}
func makeVerifyPKCS1v15(hash crypto.Hash) rsaVerifyFunc {
return rsaVerifyFunc(func(payload, signature []byte, key *rsa.PublicKey) error {
h := hash.New()
h.Write(payload)
return rsa.VerifyPKCS1v15(key, hash, h.Sum(nil), signature)
})
}
func makeVerifyPSS(hash crypto.Hash) rsaVerifyFunc {
return rsaVerifyFunc(func(payload, signature []byte, key *rsa.PublicKey) error {
h := hash.New()
h.Write(payload)
return rsa.VerifyPSS(key, hash, h.Sum(nil), signature, nil)
})
}
func newRSA(alg jwa.SignatureAlgorithm) (*RSAVerifier, error) {
verifyfn, ok := rsaVerifyFuncs[alg]
if !ok {
return nil, errors.Errorf(`unsupported algorithm while trying to create RSA verifier: %s`, alg)
}
return &RSAVerifier{
verify: verifyfn,
}, nil
}
// Verify checks if a JWS is valid.
func (v RSAVerifier) Verify(payload, signature []byte, key interface{}) error {
if key == nil {
return errors.New(`missing public key while verifying payload`)
}
rsaKey, ok := key.(*rsa.PublicKey)
if !ok {
return errors.Errorf(`invalid key type %T. *rsa.PublicKey is required`, key)
}
return v.verify(payload, signature, rsaKey)
}

View File

@@ -0,0 +1,22 @@
package verify
import (
"github.com/pkg/errors"
"github.com/open-policy-agent/opa/topdown/internal/jwx/jwa"
)
// New creates a new JWS verifier using the specified algorithm
// and the public key
func New(alg jwa.SignatureAlgorithm) (Verifier, error) {
switch alg {
case jwa.RS256, jwa.RS384, jwa.RS512, jwa.PS256, jwa.PS384, jwa.PS512:
return newRSA(alg)
case jwa.ES256, jwa.ES384, jwa.ES512:
return newECDSA(alg)
case jwa.HS256, jwa.HS384, jwa.HS512:
return newHMAC(alg)
default:
return nil, errors.Errorf(`unsupported signature algorithm: %s`, alg)
}
}