857 lines
25 KiB
Go
857 lines
25 KiB
Go
//go:generate ../tools/cmd/genjws.sh
|
|
|
|
// 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.Sign(payload, jws.WithKey(algorithm, key))
|
|
// jws.Verify(serialized, jws.WithKey(algorithm, key))
|
|
//
|
|
// To sign, simply use `jws.Sign`. `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.Sign`
|
|
// 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 (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"crypto/ed25519"
|
|
"crypto/rsa"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
|
|
"github.com/lestrrat-go/blackmagic"
|
|
"github.com/lestrrat-go/jwx/v2/internal/base64"
|
|
"github.com/lestrrat-go/jwx/v2/internal/json"
|
|
"github.com/lestrrat-go/jwx/v2/internal/pool"
|
|
"github.com/lestrrat-go/jwx/v2/jwa"
|
|
"github.com/lestrrat-go/jwx/v2/jwk"
|
|
"github.com/lestrrat-go/jwx/v2/x25519"
|
|
)
|
|
|
|
var registry = json.NewRegistry()
|
|
|
|
type payloadSigner struct {
|
|
signer Signer
|
|
key interface{}
|
|
protected Headers
|
|
public Headers
|
|
}
|
|
|
|
func (s *payloadSigner) Sign(payload []byte) ([]byte, error) {
|
|
return s.signer.Sign(payload, s.key)
|
|
}
|
|
|
|
func (s *payloadSigner) Algorithm() jwa.SignatureAlgorithm {
|
|
return s.signer.Algorithm()
|
|
}
|
|
|
|
func (s *payloadSigner) ProtectedHeader() Headers {
|
|
return s.protected
|
|
}
|
|
|
|
func (s *payloadSigner) PublicHeader() Headers {
|
|
return s.public
|
|
}
|
|
|
|
var signers = make(map[jwa.SignatureAlgorithm]Signer)
|
|
var muSigner = &sync.Mutex{}
|
|
|
|
func makeSigner(alg jwa.SignatureAlgorithm, key interface{}, public, protected Headers) (*payloadSigner, error) {
|
|
muSigner.Lock()
|
|
signer, ok := signers[alg]
|
|
if !ok {
|
|
v, err := NewSigner(alg)
|
|
if err != nil {
|
|
muSigner.Unlock()
|
|
return nil, fmt.Errorf(`failed to create payload signer: %w`, err)
|
|
}
|
|
signers[alg] = v
|
|
signer = v
|
|
}
|
|
muSigner.Unlock()
|
|
|
|
return &payloadSigner{
|
|
signer: signer,
|
|
key: key,
|
|
public: public,
|
|
protected: protected,
|
|
}, nil
|
|
}
|
|
|
|
const (
|
|
fmtInvalid = iota
|
|
fmtCompact
|
|
fmtJSON
|
|
fmtJSONPretty
|
|
fmtMax
|
|
)
|
|
|
|
// silence linters
|
|
var _ = fmtInvalid
|
|
var _ = fmtMax
|
|
|
|
func validateKeyBeforeUse(key interface{}) error {
|
|
jwkKey, ok := key.(jwk.Key)
|
|
if !ok {
|
|
converted, err := jwk.FromRaw(key)
|
|
if err != nil {
|
|
return fmt.Errorf(`could not convert key of type %T to jwk.Key for validation: %w`, key, err)
|
|
}
|
|
jwkKey = converted
|
|
}
|
|
return jwkKey.Validate()
|
|
}
|
|
|
|
// Sign generates a JWS message for the given payload and returns
|
|
// it in serialized form, which can be in either compact or
|
|
// JSON format. Default is compact.
|
|
//
|
|
// You must pass at least one key to `jws.Sign()` by using `jws.WithKey()`
|
|
// option.
|
|
//
|
|
// jws.Sign(payload, jws.WithKey(alg, key))
|
|
// jws.Sign(payload, jws.WithJSON(), jws.WithKey(alg1, key1), jws.WithKey(alg2, key2))
|
|
//
|
|
// Note that in the second example the `jws.WithJSON()` option is
|
|
// specified as well. This is because the compact serialization
|
|
// format does not support multiple signatures, and users must
|
|
// specifically ask for the JSON serialization format.
|
|
//
|
|
// Read the documentation for `jws.WithKey()` to learn more about the
|
|
// possible values that can be used for `alg` and `key`.
|
|
//
|
|
// You may create JWS messages with the "none" (jwa.NoSignature) algorithm
|
|
// if you use the `jws.WithInsecureNoSignature()` option. This option
|
|
// can be combined with one or more signature keys, as well as the
|
|
// `jws.WithJSON()` option to generate multiple signatures (though
|
|
// the usefulness of such constructs is highly debatable)
|
|
//
|
|
// Note that this library does not allow you to successfully call `jws.Verify()` on
|
|
// signatures with the "none" algorithm. To parse these, use `jws.Parse()` instead.
|
|
//
|
|
// If you want to use a detached payload, use `jws.WithDetachedPayload()` as
|
|
// one of the options. When you use this option, you must always set the
|
|
// first parameter (`payload`) to `nil`, or the function will return an error
|
|
//
|
|
// You may also want to look at how to pass protected headers to the
|
|
// signing process, as you will likely be required to set the `b64` field
|
|
// when using detached payload.
|
|
//
|
|
// Look for options that return `jws.SignOption` or `jws.SignVerifyOption`
|
|
// for a complete list of options that can be passed to this function.
|
|
func Sign(payload []byte, options ...SignOption) ([]byte, error) {
|
|
format := fmtCompact
|
|
var signers []*payloadSigner
|
|
var detached bool
|
|
var noneSignature *payloadSigner
|
|
var validateKey bool
|
|
for _, option := range options {
|
|
//nolint:forcetypeassert
|
|
switch option.Ident() {
|
|
case identSerialization{}:
|
|
format = option.Value().(int)
|
|
case identInsecureNoSignature{}:
|
|
data := option.Value().(*withInsecureNoSignature)
|
|
// only the last one is used (we overwrite previous values)
|
|
noneSignature = &payloadSigner{
|
|
signer: noneSigner{},
|
|
protected: data.protected,
|
|
}
|
|
case identKey{}:
|
|
data := option.Value().(*withKey)
|
|
|
|
alg, ok := data.alg.(jwa.SignatureAlgorithm)
|
|
if !ok {
|
|
return nil, fmt.Errorf(`jws.Sign: expected algorithm to be of type jwa.SignatureAlgorithm but got (%[1]q, %[1]T)`, data.alg)
|
|
}
|
|
|
|
// No, we don't accept "none" here.
|
|
if alg == jwa.NoSignature {
|
|
return nil, fmt.Errorf(`jws.Sign: "none" (jwa.NoSignature) cannot be used with jws.WithKey`)
|
|
}
|
|
|
|
signer, err := makeSigner(alg, data.key, data.public, data.protected)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(`jws.Sign: failed to create signer: %w`, err)
|
|
}
|
|
signers = append(signers, signer)
|
|
case identDetachedPayload{}:
|
|
detached = true
|
|
if payload != nil {
|
|
return nil, fmt.Errorf(`jws.Sign: payload must be nil when jws.WithDetachedPayload() is specified`)
|
|
}
|
|
payload = option.Value().([]byte)
|
|
case identValidateKey{}:
|
|
validateKey = option.Value().(bool)
|
|
}
|
|
}
|
|
|
|
if noneSignature != nil {
|
|
signers = append(signers, noneSignature)
|
|
}
|
|
|
|
lsigner := len(signers)
|
|
if lsigner == 0 {
|
|
return nil, fmt.Errorf(`jws.Sign: no signers available. Specify an alogirthm and akey using jws.WithKey()`)
|
|
}
|
|
|
|
// Design note: while we could have easily set format = fmtJSON when
|
|
// lsigner > 1, I believe the decision to change serialization formats
|
|
// must be explicitly stated by the caller. Otherwise I'm pretty sure
|
|
// there would be people filing issues saying "I get JSON when I expcted
|
|
// compact serialization".
|
|
//
|
|
// Therefore, instead of making implicit format conversions, we force the
|
|
// user to spell it out as `jws.Sign(..., jws.WithJSON(), jws.WithKey(...), jws.WithKey(...))`
|
|
if format == fmtCompact && lsigner != 1 {
|
|
return nil, fmt.Errorf(`jws.Sign: cannot have multiple signers (keys) specified for compact serialization. Use only one jws.WithKey()`)
|
|
}
|
|
|
|
// Create a Message object with all the bits and bobs, and we'll
|
|
// serialize it in the end
|
|
var result Message
|
|
|
|
result.payload = payload
|
|
|
|
result.signatures = make([]*Signature, 0, len(signers))
|
|
for i, signer := range signers {
|
|
protected := signer.ProtectedHeader()
|
|
if protected == nil {
|
|
protected = NewHeaders()
|
|
}
|
|
|
|
if err := protected.Set(AlgorithmKey, signer.Algorithm()); err != nil {
|
|
return nil, fmt.Errorf(`failed to set "alg" header: %w`, err)
|
|
}
|
|
|
|
if key, ok := signer.key.(jwk.Key); ok {
|
|
if kid := key.KeyID(); kid != "" {
|
|
if err := protected.Set(KeyIDKey, kid); err != nil {
|
|
return nil, fmt.Errorf(`failed to set "kid" header: %w`, err)
|
|
}
|
|
}
|
|
}
|
|
sig := &Signature{
|
|
headers: signer.PublicHeader(),
|
|
protected: protected,
|
|
// cheat. FIXXXXXXMEEEEEE
|
|
detached: detached,
|
|
}
|
|
|
|
if validateKey {
|
|
if err := validateKeyBeforeUse(signer.key); err != nil {
|
|
return nil, fmt.Errorf(`jws.Verify: %w`, err)
|
|
}
|
|
}
|
|
_, _, err := sig.Sign(payload, signer.signer, signer.key)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(`failed to generate signature for signer #%d (alg=%s): %w`, i, signer.Algorithm(), err)
|
|
}
|
|
|
|
result.signatures = append(result.signatures, sig)
|
|
}
|
|
|
|
switch format {
|
|
case fmtJSON:
|
|
return json.Marshal(result)
|
|
case fmtJSONPretty:
|
|
return json.MarshalIndent(result, "", " ")
|
|
case fmtCompact:
|
|
// Take the only signature object, and convert it into a Compact
|
|
// serialization format
|
|
var compactOpts []CompactOption
|
|
if detached {
|
|
compactOpts = append(compactOpts, WithDetached(detached))
|
|
}
|
|
return Compact(&result, compactOpts...)
|
|
default:
|
|
return nil, fmt.Errorf(`jws.Sign: invalid serialization format`)
|
|
}
|
|
}
|
|
|
|
var allowNoneWhitelist = jwk.WhitelistFunc(func(string) bool {
|
|
return false
|
|
})
|
|
|
|
// Verify checks if the given JWS message is verifiable using `alg` and `key`.
|
|
// `key` may be a "raw" key (e.g. rsa.PublicKey) or a jwk.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 generate a
|
|
// `Verifier` in `verify` subpackage, and call `Verify` method on it.
|
|
// If you need to access signatures and JOSE headers in a JWS message,
|
|
// use `Parse` function to get `Message` object.
|
|
//
|
|
// Because the use of "none" (jwa.NoSignature) algorithm is strongly discouraged,
|
|
// this function DOES NOT consider it a success when `{"alg":"none"}` is
|
|
// encountered in the message (it would also be counter intuitive when the code says
|
|
// you _verified_ something when in fact it did no such thing). If you want to
|
|
// accept messages with "none" signature algorithm, use `jws.Parse` to get the
|
|
// raw JWS message.
|
|
func Verify(buf []byte, options ...VerifyOption) ([]byte, error) {
|
|
var dst *Message
|
|
var detachedPayload []byte
|
|
var keyProviders []KeyProvider
|
|
var keyUsed interface{}
|
|
var validateKey bool
|
|
|
|
ctx := context.Background()
|
|
|
|
//nolint:forcetypeassert
|
|
for _, option := range options {
|
|
switch option.Ident() {
|
|
case identMessage{}:
|
|
dst = option.Value().(*Message)
|
|
case identDetachedPayload{}:
|
|
detachedPayload = option.Value().([]byte)
|
|
case identKey{}:
|
|
pair := option.Value().(*withKey)
|
|
alg, ok := pair.alg.(jwa.SignatureAlgorithm)
|
|
if !ok {
|
|
return nil, fmt.Errorf(`WithKey() option must be specified using jwa.SignatureAlgorithm (got %T)`, pair.alg)
|
|
}
|
|
keyProviders = append(keyProviders, &staticKeyProvider{
|
|
alg: alg,
|
|
key: pair.key,
|
|
})
|
|
case identKeyProvider{}:
|
|
keyProviders = append(keyProviders, option.Value().(KeyProvider))
|
|
case identKeyUsed{}:
|
|
keyUsed = option.Value()
|
|
case identContext{}:
|
|
ctx = option.Value().(context.Context)
|
|
case identValidateKey{}:
|
|
validateKey = option.Value().(bool)
|
|
default:
|
|
return nil, fmt.Errorf(`invalid jws.VerifyOption %q passed`, `With`+strings.TrimPrefix(fmt.Sprintf(`%T`, option.Ident()), `jws.ident`))
|
|
}
|
|
}
|
|
|
|
if len(keyProviders) < 1 {
|
|
return nil, fmt.Errorf(`jws.Verify: no key providers have been provided (see jws.WithKey(), jws.WithKeySet(), jws.WithVerifyAuto(), and jws.WithKeyProvider()`)
|
|
}
|
|
|
|
msg, err := Parse(buf)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(`failed to parse jws: %w`, err)
|
|
}
|
|
defer msg.clearRaw()
|
|
|
|
if detachedPayload != nil {
|
|
if len(msg.payload) != 0 {
|
|
return nil, fmt.Errorf(`can't specify detached payload for JWS with payload`)
|
|
}
|
|
|
|
msg.payload = detachedPayload
|
|
}
|
|
|
|
// Pre-compute the base64 encoded version of payload
|
|
var payload string
|
|
if msg.b64 {
|
|
payload = base64.EncodeToString(msg.payload)
|
|
} else {
|
|
payload = string(msg.payload)
|
|
}
|
|
|
|
verifyBuf := pool.GetBytesBuffer()
|
|
defer pool.ReleaseBytesBuffer(verifyBuf)
|
|
|
|
var errs []error
|
|
for i, sig := range msg.signatures {
|
|
verifyBuf.Reset()
|
|
|
|
var encodedProtectedHeader string
|
|
if rbp, ok := sig.protected.(interface{ rawBuffer() []byte }); ok {
|
|
if raw := rbp.rawBuffer(); raw != nil {
|
|
encodedProtectedHeader = base64.EncodeToString(raw)
|
|
}
|
|
}
|
|
|
|
if encodedProtectedHeader == "" {
|
|
protected, err := json.Marshal(sig.protected)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(`failed to marshal "protected" for signature #%d: %w`, i+1, err)
|
|
}
|
|
|
|
encodedProtectedHeader = base64.EncodeToString(protected)
|
|
}
|
|
|
|
verifyBuf.WriteString(encodedProtectedHeader)
|
|
verifyBuf.WriteByte('.')
|
|
verifyBuf.WriteString(payload)
|
|
|
|
for i, kp := range keyProviders {
|
|
var sink algKeySink
|
|
if err := kp.FetchKeys(ctx, &sink, sig, msg); err != nil {
|
|
return nil, fmt.Errorf(`key provider %d failed: %w`, i, err)
|
|
}
|
|
|
|
for _, pair := range sink.list {
|
|
// alg is converted here because pair.alg is of type jwa.KeyAlgorithm.
|
|
// this may seem ugly, but we're trying to avoid declaring separate
|
|
// structs for `alg jwa.KeyAlgorithm` and `alg jwa.SignatureAlgorithm`
|
|
//nolint:forcetypeassert
|
|
alg := pair.alg.(jwa.SignatureAlgorithm)
|
|
key := pair.key
|
|
|
|
if validateKey {
|
|
if err := validateKeyBeforeUse(key); err != nil {
|
|
return nil, fmt.Errorf(`jws.Verify: %w`, err)
|
|
}
|
|
}
|
|
verifier, err := NewVerifier(alg)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(`failed to create verifier for algorithm %q: %w`, alg, err)
|
|
}
|
|
|
|
if err := verifier.Verify(verifyBuf.Bytes(), sig.signature, key); err != nil {
|
|
errs = append(errs, err)
|
|
continue
|
|
}
|
|
|
|
if keyUsed != nil {
|
|
if err := blackmagic.AssignIfCompatible(keyUsed, key); err != nil {
|
|
return nil, fmt.Errorf(`failed to assign used key (%T) to %T: %w`, key, keyUsed, err)
|
|
}
|
|
}
|
|
|
|
if dst != nil {
|
|
*(dst) = *msg
|
|
}
|
|
|
|
return msg.payload, nil
|
|
}
|
|
}
|
|
}
|
|
return nil, &verifyError{errs: errs}
|
|
}
|
|
|
|
type verifyError struct {
|
|
// Note: when/if we can ditch Go < 1.20, we can change this to a simple
|
|
// `err error`, where the value is the result of `errors.Join()`
|
|
//
|
|
// We also need to implement Unwrap:
|
|
// func (e *verifyError) Unwrap() error {
|
|
// return e.err
|
|
//}
|
|
//
|
|
// And finally, As() can go away
|
|
errs []error
|
|
}
|
|
|
|
func (e *verifyError) Error() string {
|
|
return `could not verify message using any of the signatures or keys`
|
|
}
|
|
|
|
func (e *verifyError) As(target interface{}) bool {
|
|
for _, err := range e.errs {
|
|
if errors.As(err, target) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// get the value of b64 header field.
|
|
// If the field does not exist, returns true (default)
|
|
// Otherwise return the value specified by the header field.
|
|
func getB64Value(hdr Headers) bool {
|
|
b64raw, ok := hdr.Get("b64")
|
|
if !ok {
|
|
return true // default
|
|
}
|
|
|
|
b64, ok := b64raw.(bool) // default
|
|
if !ok {
|
|
return false
|
|
}
|
|
return b64
|
|
}
|
|
|
|
// This is an "optimized" io.ReadAll(). It will attempt to read
|
|
// all of the contents from the reader IF the reader is of a certain
|
|
// concrete type.
|
|
func readAll(rdr io.Reader) ([]byte, bool) {
|
|
switch rdr.(type) {
|
|
case *bytes.Reader, *bytes.Buffer, *strings.Reader:
|
|
data, err := io.ReadAll(rdr)
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
return data, true
|
|
default:
|
|
return nil, false
|
|
}
|
|
}
|
|
|
|
// Parse parses contents from the given source and creates a jws.Message
|
|
// struct. The input can be in either compact or full JSON serialization.
|
|
//
|
|
// Parse() currently does not take any options, but the API accepts it
|
|
// in anticipation of future addition.
|
|
func Parse(src []byte, _ ...ParseOption) (*Message, error) {
|
|
for i := 0; i < len(src); i++ {
|
|
r := rune(src[i])
|
|
if r >= utf8.RuneSelf {
|
|
r, _ = utf8.DecodeRune(src)
|
|
}
|
|
if !unicode.IsSpace(r) {
|
|
if r == '{' {
|
|
return parseJSON(src)
|
|
}
|
|
return parseCompact(src)
|
|
}
|
|
}
|
|
return nil, fmt.Errorf(`invalid byte sequence`)
|
|
}
|
|
|
|
// Parse parses contents from the given source and creates a jws.Message
|
|
// struct. The input can be in either compact or full JSON serialization.
|
|
func ParseString(src string) (*Message, error) {
|
|
return Parse([]byte(src))
|
|
}
|
|
|
|
// Parse parses contents from the given source and creates a jws.Message
|
|
// struct. The input can be in either compact or full JSON serialization.
|
|
func ParseReader(src io.Reader) (*Message, error) {
|
|
if data, ok := readAll(src); ok {
|
|
return Parse(data)
|
|
}
|
|
|
|
rdr := bufio.NewReader(src)
|
|
var first rune
|
|
for {
|
|
r, _, err := rdr.ReadRune()
|
|
if err != nil {
|
|
return nil, fmt.Errorf(`failed to read rune: %w`, err)
|
|
}
|
|
if !unicode.IsSpace(r) {
|
|
first = r
|
|
if err := rdr.UnreadRune(); err != nil {
|
|
return nil, fmt.Errorf(`failed to unread rune: %w`, err)
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
var parser func(io.Reader) (*Message, error)
|
|
if first == '{' {
|
|
parser = parseJSONReader
|
|
} else {
|
|
parser = parseCompactReader
|
|
}
|
|
|
|
m, err := parser(rdr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(`failed to parse jws message: %w`, err)
|
|
}
|
|
|
|
return m, nil
|
|
}
|
|
|
|
func parseJSONReader(src io.Reader) (result *Message, err error) {
|
|
var m Message
|
|
if err := json.NewDecoder(src).Decode(&m); err != nil {
|
|
return nil, fmt.Errorf(`failed to unmarshal jws message: %w`, err)
|
|
}
|
|
return &m, nil
|
|
}
|
|
|
|
func parseJSON(data []byte) (result *Message, err error) {
|
|
var m Message
|
|
if err := json.Unmarshal(data, &m); err != nil {
|
|
return nil, fmt.Errorf(`failed to unmarshal jws message: %w`, err)
|
|
}
|
|
return &m, nil
|
|
}
|
|
|
|
// SplitCompact splits a JWT and returns its three parts
|
|
// separately: protected headers, payload and signature.
|
|
func SplitCompact(src []byte) ([]byte, []byte, []byte, error) {
|
|
parts := bytes.Split(src, []byte("."))
|
|
if len(parts) < 3 {
|
|
return nil, nil, nil, fmt.Errorf(`invalid number of segments`)
|
|
}
|
|
return parts[0], parts[1], parts[2], nil
|
|
}
|
|
|
|
// SplitCompactString splits a JWT and returns its three parts
|
|
// separately: protected headers, payload and signature.
|
|
func SplitCompactString(src string) ([]byte, []byte, []byte, error) {
|
|
parts := strings.Split(src, ".")
|
|
if len(parts) < 3 {
|
|
return nil, nil, nil, fmt.Errorf(`invalid number of segments`)
|
|
}
|
|
return []byte(parts[0]), []byte(parts[1]), []byte(parts[2]), nil
|
|
}
|
|
|
|
// SplitCompactReader splits a JWT and returns its three parts
|
|
// separately: protected headers, payload and signature.
|
|
func SplitCompactReader(rdr io.Reader) ([]byte, []byte, []byte, error) {
|
|
if data, ok := readAll(rdr); ok {
|
|
return SplitCompact(data)
|
|
}
|
|
|
|
var protected []byte
|
|
var payload []byte
|
|
var signature []byte
|
|
var periods int
|
|
var state int
|
|
|
|
buf := make([]byte, 4096)
|
|
var sofar []byte
|
|
|
|
for {
|
|
// read next bytes
|
|
n, err := rdr.Read(buf)
|
|
// return on unexpected read error
|
|
if err != nil && err != io.EOF {
|
|
return nil, nil, nil, fmt.Errorf(`unexpected end of input: %w`, err)
|
|
}
|
|
|
|
// append to current buffer
|
|
sofar = append(sofar, buf[:n]...)
|
|
// loop to capture multiple '.' in current buffer
|
|
for loop := true; loop; {
|
|
var i = bytes.IndexByte(sofar, '.')
|
|
if i == -1 && err != io.EOF {
|
|
// no '.' found -> exit and read next bytes (outer loop)
|
|
loop = false
|
|
continue
|
|
} else if i == -1 && err == io.EOF {
|
|
// no '.' found -> process rest and exit
|
|
i = len(sofar)
|
|
loop = false
|
|
} else {
|
|
// '.' found
|
|
periods++
|
|
}
|
|
|
|
// Reaching this point means we have found a '.' or EOF and process the rest of the buffer
|
|
switch state {
|
|
case 0:
|
|
protected = sofar[:i]
|
|
state++
|
|
case 1:
|
|
payload = sofar[:i]
|
|
state++
|
|
case 2:
|
|
signature = sofar[:i]
|
|
}
|
|
// Shorten current buffer
|
|
if len(sofar) > i {
|
|
sofar = sofar[i+1:]
|
|
}
|
|
}
|
|
// Exit on EOF
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
}
|
|
if periods != 2 {
|
|
return nil, nil, nil, fmt.Errorf(`invalid number of segments`)
|
|
}
|
|
|
|
return protected, payload, signature, nil
|
|
}
|
|
|
|
// parseCompactReader parses a JWS value serialized via compact serialization.
|
|
func parseCompactReader(rdr io.Reader) (m *Message, err error) {
|
|
protected, payload, signature, err := SplitCompactReader(rdr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(`invalid compact serialization format: %w`, err)
|
|
}
|
|
return parse(protected, payload, signature)
|
|
}
|
|
|
|
func parseCompact(data []byte) (m *Message, err error) {
|
|
protected, payload, signature, err := SplitCompact(data)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(`invalid compact serialization format: %w`, err)
|
|
}
|
|
return parse(protected, payload, signature)
|
|
}
|
|
|
|
func parse(protected, payload, signature []byte) (*Message, error) {
|
|
decodedHeader, err := base64.Decode(protected)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(`failed to decode protected headers: %w`, err)
|
|
}
|
|
|
|
hdr := NewHeaders()
|
|
if err := json.Unmarshal(decodedHeader, hdr); err != nil {
|
|
return nil, fmt.Errorf(`failed to parse JOSE headers: %w`, err)
|
|
}
|
|
|
|
var decodedPayload []byte
|
|
b64 := getB64Value(hdr)
|
|
if !b64 {
|
|
decodedPayload = payload
|
|
} else {
|
|
v, err := base64.Decode(payload)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(`failed to decode payload: %w`, err)
|
|
}
|
|
decodedPayload = v
|
|
}
|
|
|
|
decodedSignature, err := base64.Decode(signature)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(`failed to decode signature: %w`, err)
|
|
}
|
|
|
|
var msg Message
|
|
msg.payload = decodedPayload
|
|
msg.signatures = append(msg.signatures, &Signature{
|
|
protected: hdr,
|
|
signature: decodedSignature,
|
|
})
|
|
msg.b64 = b64
|
|
return &msg, nil
|
|
}
|
|
|
|
// RegisterCustomField allows users to specify that a private field
|
|
// be decoded as an instance of the specified type. This option has
|
|
// a global effect.
|
|
//
|
|
// For example, suppose you have a custom field `x-birthday`, which
|
|
// you want to represent as a string formatted in RFC3339 in JSON,
|
|
// but want it back as `time.Time`.
|
|
//
|
|
// In that case you would register a custom field as follows
|
|
//
|
|
// jwe.RegisterCustomField(`x-birthday`, timeT)
|
|
//
|
|
// Then `hdr.Get("x-birthday")` will still return an `interface{}`,
|
|
// but you can convert its type to `time.Time`
|
|
//
|
|
// bdayif, _ := hdr.Get(`x-birthday`)
|
|
// bday := bdayif.(time.Time)
|
|
func RegisterCustomField(name string, object interface{}) {
|
|
registry.Register(name, object)
|
|
}
|
|
|
|
// Helpers for signature verification
|
|
var rawKeyToKeyType = make(map[reflect.Type]jwa.KeyType)
|
|
var keyTypeToAlgorithms = make(map[jwa.KeyType][]jwa.SignatureAlgorithm)
|
|
|
|
func init() {
|
|
rawKeyToKeyType[reflect.TypeOf([]byte(nil))] = jwa.OctetSeq
|
|
rawKeyToKeyType[reflect.TypeOf(ed25519.PublicKey(nil))] = jwa.OKP
|
|
rawKeyToKeyType[reflect.TypeOf(rsa.PublicKey{})] = jwa.RSA
|
|
rawKeyToKeyType[reflect.TypeOf((*rsa.PublicKey)(nil))] = jwa.RSA
|
|
rawKeyToKeyType[reflect.TypeOf(ecdsa.PublicKey{})] = jwa.EC
|
|
rawKeyToKeyType[reflect.TypeOf((*ecdsa.PublicKey)(nil))] = jwa.EC
|
|
|
|
addAlgorithmForKeyType(jwa.OKP, jwa.EdDSA)
|
|
for _, alg := range []jwa.SignatureAlgorithm{jwa.HS256, jwa.HS384, jwa.HS512} {
|
|
addAlgorithmForKeyType(jwa.OctetSeq, alg)
|
|
}
|
|
for _, alg := range []jwa.SignatureAlgorithm{jwa.RS256, jwa.RS384, jwa.RS512, jwa.PS256, jwa.PS384, jwa.PS512} {
|
|
addAlgorithmForKeyType(jwa.RSA, alg)
|
|
}
|
|
for _, alg := range []jwa.SignatureAlgorithm{jwa.ES256, jwa.ES384, jwa.ES512} {
|
|
addAlgorithmForKeyType(jwa.EC, alg)
|
|
}
|
|
}
|
|
|
|
func addAlgorithmForKeyType(kty jwa.KeyType, alg jwa.SignatureAlgorithm) {
|
|
keyTypeToAlgorithms[kty] = append(keyTypeToAlgorithms[kty], alg)
|
|
}
|
|
|
|
// AlgorithmsForKey returns the possible signature algorithms that can
|
|
// be used for a given key. It only takes in consideration keys/algorithms
|
|
// for verification purposes, as this is the only usage where one may need
|
|
// dynamically figure out which method to use.
|
|
func AlgorithmsForKey(key interface{}) ([]jwa.SignatureAlgorithm, error) {
|
|
var kty jwa.KeyType
|
|
switch key := key.(type) {
|
|
case jwk.Key:
|
|
kty = key.KeyType()
|
|
case rsa.PublicKey, *rsa.PublicKey, rsa.PrivateKey, *rsa.PrivateKey:
|
|
kty = jwa.RSA
|
|
case ecdsa.PublicKey, *ecdsa.PublicKey, ecdsa.PrivateKey, *ecdsa.PrivateKey:
|
|
kty = jwa.EC
|
|
case ed25519.PublicKey, ed25519.PrivateKey, x25519.PublicKey, x25519.PrivateKey:
|
|
kty = jwa.OKP
|
|
case []byte:
|
|
kty = jwa.OctetSeq
|
|
default:
|
|
return nil, fmt.Errorf(`invalid key %T`, key)
|
|
}
|
|
|
|
algs, ok := keyTypeToAlgorithms[kty]
|
|
if !ok {
|
|
return nil, fmt.Errorf(`invalid key type %q`, kty)
|
|
}
|
|
return algs, nil
|
|
}
|
|
|
|
// Because the keys defined in github.com/lestrrat-go/jwx/jwk may also implement
|
|
// crypto.Signer, it would be possible for to mix up key types when signing/verifying
|
|
// for example, when we specify jws.WithKey(jwa.RSA256, cryptoSigner), the cryptoSigner
|
|
// can be for RSA, or any other type that implements crypto.Signer... even if it's for the
|
|
// wrong algorithm.
|
|
//
|
|
// These functions are there to differentiate between the valid KNOWN key types.
|
|
// For any other key type that is outside of the Go std library and our own code,
|
|
// we must rely on the user to be vigilant.
|
|
//
|
|
// Notes: symmetric keys are obviously not part of this. for v2 OKP keys,
|
|
// x25519 does not implement Sign()
|
|
func isValidRSAKey(key interface{}) bool {
|
|
switch key.(type) {
|
|
case
|
|
ecdsa.PrivateKey, *ecdsa.PrivateKey,
|
|
ed25519.PrivateKey,
|
|
jwk.ECDSAPrivateKey, jwk.OKPPrivateKey:
|
|
// these are NOT ok
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func isValidECDSAKey(key interface{}) bool {
|
|
switch key.(type) {
|
|
case
|
|
ed25519.PrivateKey,
|
|
rsa.PrivateKey, *rsa.PrivateKey,
|
|
jwk.RSAPrivateKey, jwk.OKPPrivateKey:
|
|
// these are NOT ok
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func isValidEDDSAKey(key interface{}) bool {
|
|
switch key.(type) {
|
|
case
|
|
ecdsa.PrivateKey, *ecdsa.PrivateKey,
|
|
rsa.PrivateKey, *rsa.PrivateKey,
|
|
jwk.RSAPrivateKey, jwk.ECDSAPrivateKey:
|
|
// these are NOT ok
|
|
return false
|
|
}
|
|
return true
|
|
}
|