jws

package
v3.0.0-alpha1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 1, 2024 License: MIT Imports: 37 Imported by: 3

README

JWS Go Reference

Package jws implements JWS as described in RFC7515 and RFC7797

  • Parse and generate compact or JSON serializations
  • Sign and verify arbitrary payload
  • Use any of the keys supported in github.com/lestrrat-go/jwx/v3/jwk
  • Add arbitrary fields in the JWS object
  • Ability to add/replace existing signature methods
  • Respect "b64" settings for RFC7797

How-to style documentation can be found in the docs directory.

Examples are located in the examples directory (jws_example_test.go)

Supported signature algorithms:

Algorithm Supported? Constant in jwa
HMAC using SHA-256 YES jwa.HS256
HMAC using SHA-384 YES jwa.HS384
HMAC using SHA-512 YES jwa.HS512
RSASSA-PKCS-v1.5 using SHA-256 YES jwa.RS256
RSASSA-PKCS-v1.5 using SHA-384 YES jwa.RS384
RSASSA-PKCS-v1.5 using SHA-512 YES jwa.RS512
ECDSA using P-256 and SHA-256 YES jwa.ES256
ECDSA using P-384 and SHA-384 YES jwa.ES384
ECDSA using P-521 and SHA-512 YES jwa.ES512
ECDSA using secp256k1 and SHA-256 (2) YES jwa.ES256K
RSASSA-PSS using SHA256 and MGF1-SHA256 YES jwa.PS256
RSASSA-PSS using SHA384 and MGF1-SHA384 YES jwa.PS384
RSASSA-PSS using SHA512 and MGF1-SHA512 YES jwa.PS512
EdDSA (1) YES jwa.EdDSA
  • Note 1: Experimental
  • Note 2: Experimental, and must be toggled using -tags jwx_es256k build tag

SYNOPSIS

Sign and verify arbitrary data

import(
  "crypto/rand"
  "crypto/rsa"
  "log"

  "github.com/lestrrat-go/jwx/v3/jwa"
  "github.com/lestrrat-go/jwx/v3/jws"
)

func main() {
  privkey, err := rsa.GenerateKey(rand.Reader, 2048)
  if err != nil {
    log.Printf("failed to generate private key: %s", err)
    return
  }

  buf, err := jws.Sign([]byte("Lorem ipsum"), jws.WithKey(jwa.RS256, privkey))
  if err != nil {
    log.Printf("failed to created JWS message: %s", err)
    return
  }

  // When you receive a JWS message, you can verify the signature
  // and grab the payload sent in the message in one go:
  verified, err := jws.Verify(buf, jws.WithKey(jwa.RS256, &privkey.PublicKey))
  if err != nil {
    log.Printf("failed to verify message: %s", err)
    return
  }

  log.Printf("signed message verified! -> %s", verified)
}

Programmatically manipulate jws.Message

func ExampleMessage() {
  // initialization for the following variables have been omitted.
  // please see jws_example_test.go for details
  var decodedPayload, decodedSig1, decodedSig2 []byte
  var public1, protected1, public2, protected2 jws.Header

  // Construct a message. DO NOT use values that are base64 encoded
  m := jws.NewMessage().
    SetPayload(decodedPayload).
    AppendSignature(
      jws.NewSignature().
        SetSignature(decodedSig1).
        SetProtectedHeaders(public1).
        SetPublicHeaders(protected1),
    ).
    AppendSignature(
      jws.NewSignature().
        SetSignature(decodedSig2).
        SetProtectedHeaders(public2).
        SetPublicHeaders(protected2),
    )

  buf, err := json.MarshalIndent(m, "", "  ")
  if err != nil {
    fmt.Printf("%s\n", err)
    return
  }

  _ = buf
}

Documentation

Overview

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.

Index

Constants

View Source
const (
	AlgorithmKey              = "alg"
	ContentTypeKey            = "cty"
	CriticalKey               = "crit"
	JWKKey                    = "jwk"
	JWKSetURLKey              = "jku"
	KeyIDKey                  = "kid"
	TypeKey                   = "typ"
	X509CertChainKey          = "x5c"
	X509CertThumbprintKey     = "x5t"
	X509CertThumbprintS256Key = "x5t#S256"
	X509URLKey                = "x5u"
)

Variables

This section is empty.

Functions

func AlgorithmsForKey

func AlgorithmsForKey(key interface{}) ([]jwa.SignatureAlgorithm, error)

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 Compact

func Compact(msg *Message, options ...CompactOption) ([]byte, error)

Compact generates a JWS message in compact serialization format from `*jws.Message` object. The object contain exactly one signature, or an error is returned.

If using a detached payload, the payload must already be stored in the `*jws.Message` object, and the `jws.WithDetached()` option must be passed to the function.

func ParseError

func ParseError() error

ParseError returns an error that can be passed to `errors.Is` to check if the error is a parse error.

func RegisterCustomField

func RegisterCustomField(name string, object interface{})

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 such case you would register a custom field as follows

jws.RegisterCustomField(`x-birthday`, time.Time{})

Then you can use a `time.Time` variable to extract the value of `x-birthday` field, instead of having to use `interface{}` and later convert it to `time.Time`

var bday time.Time
_ = hdr.Get(`x-birthday`, &bday)

If you need a more fine-tuned control over the decoding process, you can register a `CustomDecoder`. For example, below shows how to register a decoder that can parse RFC1123 format string:

jws.RegisterCustomField(`x-birthday`, jws.CustomDecodeFunc(func(data []byte) (interface{}, error) {
  return time.Parse(time.RFC1123, string(data))
}))

Please note that use of custom fields can be problematic if you are using a library that does not implement MarshalJSON/UnmarshalJSON and you try to roundtrip from an object to JSON, and then back to an object. For example, in the above example, you can _parse_ time values formatted in the format specified in RFC822, but when you convert an object into JSON, it will be formatted in RFC3339, because that's what `time.Time` likes to do. To avoid this, it's always better to use a custom type that wraps your desired type (in this case `time.Time`) and implement MarshalJSON and UnmashalJSON.

func RegisterSigner

func RegisterSigner(alg jwa.SignatureAlgorithm, f SignerFactory)

RegisterSigner is used to register a factory object that creates Signer objects based on the given algorithm. Previous object instantiated by the factory is discarded.

For example, if you would like to provide a custom signer for jwa.EdDSA, use this function to register a `SignerFactory` (probably in your `init()`)

Unlike the `UnregisterSigner` function, this function automatically calls `jwa.RegisterSignatureAlgorithm` to register the algorithm in this module's algorithm database.

func RegisterVerifier

func RegisterVerifier(alg jwa.SignatureAlgorithm, f VerifierFactory)

RegisterVerifier is used to register a factory object that creates Verifier objects based on the given algorithm.

For example, if you would like to provide a custom verifier for jwa.EdDSA, use this function to register a `VerifierFactory` (probably in your `init()`)

Unlike the `UnregisterVerifier` function, this function automatically calls `jwa.RegisterSignatureAlgorithm` to register the algorithm in this module's algorithm database.

func Sign

func Sign(payload []byte, options ...SignOption) ([]byte, error)

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.

You can use `errors.Is` with `jws.SignError()` to check if an error is from this function.

func SignError

func SignError() error

SignError returns an error that can be passed to `errors.Is` to check if the error is a sign error.

func SplitCompact

func SplitCompact(src []byte) ([]byte, []byte, []byte, error)

SplitCompact splits a JWT and returns its three parts separately: protected headers, payload and signature.

On error, returns a jws.ParseError.

func SplitCompactReader

func SplitCompactReader(rdr io.Reader) ([]byte, []byte, []byte, error)

SplitCompactReader splits a JWT and returns its three parts separately: protected headers, payload and signature.

On error, returns a jws.ParseError.

func SplitCompactString

func SplitCompactString(src string) ([]byte, []byte, []byte, error)

SplitCompactString splits a JWT and returns its three parts separately: protected headers, payload and signature.

On error, returns a jws.ParseError.

func UnregisterSigner

func UnregisterSigner(alg jwa.SignatureAlgorithm)

UnregisterSigner removes the signer factory associated with the given algorithm, as well as the signer instance created by the factory.

Note that when you call this function, the algorithm itself is not automatically unregistered from this module's algorithm database. This is because the algorithm may still be required for verification or some other operation (however unlikely, it is still possible). Therefore, in order to completely remove the algorithm, you must call `jwa.UnregisterSignatureAlgorithm` yourself.

func UnregisterVerifier

func UnregisterVerifier(alg jwa.SignatureAlgorithm)

UnregisterVerifier removes the signer factory associated with the given algorithm.

Note that when you call this function, the algorithm itself is not automatically unregistered from this module's algorithm database. This is because the algorithm may still be required for signing or some other operation (however unlikely, it is still possible). Therefore, in order to completely remove the algorithm, you must call `jwa.UnregisterSignatureAlgorithm` yourself.

func VerificationError

func VerificationError() error

VerificationError returns an error that can be passed to `errors.Is` to check if the error is a verification error.

func Verify

func Verify(buf []byte, options ...VerifyOption) ([]byte, error)

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 counterintuitive when the code says it _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.

The error returned by this function is of type can be checked against `jws.VerifyError()` and `jws.VerificationError()`. The latter is returned when the verification process itself fails (e.g. invalid signature, wrong key), while the former is returned when any other part of the `jws.Verify()` function fails.

func VerifyError

func VerifyError() error

VerifyError returns an error that can be passed to `errors.Is` to check if the error is a verify error.

Types

type CompactOption

type CompactOption interface {
	Option
	// contains filtered or unexported methods
}

CompactOption describes options that can be passed to `jws.Compact`

func WithDetached

func WithDetached(v bool) CompactOption

WithDetached specifies that the `jws.Message` should be serialized in JWS compact serialization with detached payload. The resulting octet sequence will not contain the payload section.

type CustomDecodeFunc

type CustomDecodeFunc = json.CustomDecodeFunc

type CustomDecoder

type CustomDecoder = json.CustomDecoder

type DecodeCtx

type DecodeCtx interface {
	CollectRaw() bool
}

type HMACSigner

type HMACSigner struct {
	// contains filtered or unexported fields
}

HMACSigner uses crypto/hmac to sign the payloads.

func (HMACSigner) Algorithm

func (s HMACSigner) Algorithm() jwa.SignatureAlgorithm

func (HMACSigner) Sign

func (s HMACSigner) Sign(payload []byte, key interface{}) ([]byte, error)

type HMACVerifier

type HMACVerifier struct {
	// contains filtered or unexported fields
}

func (HMACVerifier) Verify

func (v HMACVerifier) Verify(payload, signature []byte, key interface{}) (err error)

type Headers

type Headers interface {
	Algorithm() (jwa.SignatureAlgorithm, bool)
	ContentType() (string, bool)
	Critical() ([]string, bool)
	JWK() (jwk.Key, bool)
	JWKSetURL() (string, bool)
	KeyID() (string, bool)
	Type() (string, bool)
	X509CertChain() (*cert.Chain, bool)
	X509CertThumbprint() (string, bool)
	X509CertThumbprintS256() (string, bool)
	X509URL() (string, bool)
	Copy(Headers) error
	Merge(Headers) (Headers, error)
	// Get is used to extract the value of any field, including non-standard fields, out of the header.
	//
	// The first argument is the name of the field. The second argument is a pointer
	// to a variable that will receive the value of the field. The method returns
	// an error if the field does not exist, or if the value cannot be assigned to
	// the destination variable. Note that a field is considered to "exist" even if
	// the value is empty-ish (e.g. 0, false, ""), as long as it is explicitly set.
	Get(string, interface{}) error
	Set(string, interface{}) error
	Remove(string) error
	// Has returns true if the specified header has a value, even if
	// the value is empty-ish (e.g. 0, false, "")  as long as it has been
	// explicitly set.
	Has(string) bool
	Keys() []string
}

Headers describe a standard JWS Header set. It is part of the JWS message and is used to represet both Public or Protected headers, which in turn can be found in each Signature object. If you are not sure how this works, it is strongly recommended that you read RFC7515, especially the section that describes the full JSON serialization format of JWS messages.

In most cases, you likely want to use the protected headers, as this is part of the signed content.

func NewHeaders

func NewHeaders() Headers

type KeyProvider

type KeyProvider interface {
	FetchKeys(context.Context, KeySink, *Signature, *Message) error
}

KeyProvider is responsible for providing key(s) to sign or verify a payload. Multiple `jws.KeyProvider`s can be passed to `jws.Verify()` or `jws.Sign()`

`jws.Sign()` can only accept static key providers via `jws.WithKey()`, while `jws.Verify()` can accept `jws.WithKey()`, `jws.WithKeySet()`, `jws.WithVerifyAuto()`, and `jws.WithKeyProvider()`.

Understanding how this works is crucial to learn how this package works.

`jws.Sign()` is straightforward: signatures are created for each provided key.

`jws.Verify()` is a bit more involved, because there are cases you will want to compute/deduce/guess the keys that you would like to use for verification.

The first thing that `jws.Verify()` does is to collect the KeyProviders from the option list that the user provided (presented in pseudocode):

keyProviders := filterKeyProviders(options)

Then, remember that a JWS message may contain multiple signatures in the message. For each signature, we call on the KeyProviders to give us the key(s) to use on this signature:

for sig in msg.Signatures {
  for kp in keyProviders {
    kp.FetchKeys(ctx, sink, sig, msg)
    ...
  }
}

The `sink` argument passed to the KeyProvider is a temporary storage for the keys (either a jwk.Key or a "raw" key). The `KeyProvider` is responsible for sending keys into the `sink`.

When called, the `KeyProvider` created by `jws.WithKey()` sends the same key, `jws.WithKeySet()` sends keys that matches a particular `kid` and `alg`, `jws.WithVerifyAuto()` fetches a JWK from the `jku` URL, and finally `jws.WithKeyProvider()` allows you to execute arbitrary logic to provide keys. If you are providing a custom `KeyProvider`, you should execute the necessary checks or retrieval of keys, and then send the key(s) to the sink:

sink.Key(alg, key)

These keys are then retrieved and tried for each signature, until a match is found:

keys := sink.Keys()
for key in keys {
  if givenSignature == makeSignature(key, payload, ...)) {
    return OK
  }
}

type KeyProviderFunc

type KeyProviderFunc func(context.Context, KeySink, *Signature, *Message) error

KeyProviderFunc is a type of KeyProvider that is implemented by a single function. You can use this to create ad-hoc `KeyProvider` instances.

func (KeyProviderFunc) FetchKeys

func (kp KeyProviderFunc) FetchKeys(ctx context.Context, sink KeySink, sig *Signature, msg *Message) error

type KeySink

type KeySink interface {
	Key(jwa.SignatureAlgorithm, interface{})
}

KeySink is a data storage where `jws.KeyProvider` objects should send their keys to.

type Message

type Message struct {
	// contains filtered or unexported fields
}

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 payload with. You should only use this when you want to actually programmatically view the contents of the full JWS payload.

As of this version, there is one big incompatibility when using Message objects to convert between compact and JSON representations. The protected header is sometimes encoded differently from the original message and the JSON serialization that we use in Go.

For example, the protected header `eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9` decodes to

{"typ":"JWT",
  "alg":"HS256"}

However, when we parse this into a message, we create a jws.Header object, which, when we marshal into a JSON object again, becomes

{"typ":"JWT","alg":"HS256"}

Notice that serialization lacks a line break and a space between `"JWT",` and `"alg"`. This causes a problem when verifying the signatures AFTER a compact JWS message has been unmarshaled into a jws.Message.

jws.Verify() doesn't go through this step, and therefore this does not manifest itself. However, you may see this discrepancy when you manually go through these conversions, and/or use the `jwx` tool like so:

jwx jws parse message.jws | jwx jws verify --key somekey.jwk --stdin

In this scenario, the first `jwx jws parse` outputs a parsed jws.Message which is marshaled into JSON. At this point the message's protected headers and the signatures don't match.

To sign and verify, use the appropriate `Sign()` and `Verify()` functions.

func NewMessage

func NewMessage() *Message

func Parse

func Parse(src []byte, options ...ParseOption) (*Message, error)

Parse parses contents from the given source and creates a jws.Message struct. By default the input can be in either compact or full JSON serialization.

You may pass `jws.WithJSON()` and/or `jws.WithCompact()` to specify explicitly which format to use. If neither or both is specified, the function will attempt to autodetect the format. If one or the other is specified, only the specified format will be attempted.

On error, returns a jws.ParseError.

func ParseReader

func ParseReader(src io.Reader) (*Message, error)

ParseReader parses contents from the given source and creates a jws.Message struct. The input can be in either compact or full JSON serialization.

On error, returns a jws.ParseError.

func ParseString

func ParseString(src string) (*Message, error)

ParseString parses contents from the given source and creates a jws.Message struct. The input can be in either compact or full JSON serialization.

On error, returns a jws.ParseError.

func ReadFile

func ReadFile(path string, options ...ReadFileOption) (*Message, error)

func (*Message) AppendSignature

func (m *Message) AppendSignature(v *Signature) *Message

func (*Message) ClearSignatures

func (m *Message) ClearSignatures() *Message

func (*Message) DecodeCtx

func (m *Message) DecodeCtx() DecodeCtx

func (Message) LookupSignature

func (m Message) LookupSignature(kid string) []*Signature

LookupSignature looks up a particular signature entry using the `kid` value

func (Message) MarshalJSON

func (m Message) MarshalJSON() ([]byte, error)

func (Message) Payload

func (m Message) Payload() []byte

Payload returns the decoded payload

func (*Message) SetDecodeCtx

func (m *Message) SetDecodeCtx(dc DecodeCtx)

func (*Message) SetPayload

func (m *Message) SetPayload(v []byte) *Message

func (Message) Signatures

func (m Message) Signatures() []*Signature

func (*Message) UnmarshalJSON

func (m *Message) UnmarshalJSON(buf []byte) error

type Option

type Option = option.Interface

type ParseOption

type ParseOption interface {
	Option
	// contains filtered or unexported methods
}

ReadFileOption is a type of `Option` that can be passed to `jwe.Parse`

type ReadFileOption

type ReadFileOption interface {
	Option
	// contains filtered or unexported methods
}

ReadFileOption is a type of `Option` that can be passed to `jws.ReadFile`

func WithFS

func WithFS(v fs.FS) ReadFileOption

WithFS specifies the source `fs.FS` object to read the file from.

type SignOption

type SignOption interface {
	Option
	// contains filtered or unexported methods
}

SignOption describes options that can be passed to `jws.Sign`

func WithInsecureNoSignature

func WithInsecureNoSignature(options ...WithKeySuboption) SignOption

WithInsecureNoSignature creates an option that allows the user to use the "none" signature algorithm.

Please note that this is insecure, and should never be used in production (this is exactly why specifying "none"/jwa.NoSignature to `jws.WithKey()` results in an error when `jws.Sign()` is called -- we do not allow using "none" by accident)

TODO: create specific suboption set for this option

type SignVerifyOption

type SignVerifyOption interface {
	Option
	// contains filtered or unexported methods
}

SignVerifyOption describes options that can be passed to either `jws.Verify` or `jws.Sign`

func WithDetachedPayload

func WithDetachedPayload(v []byte) SignVerifyOption

WithDetachedPayload can be used to both sign or verify a JWS message with a detached payload.

When this option is used for `jws.Sign()`, the first parameter (normally the payload) must be set to `nil`.

If you have to verify using this option, you should know exactly how and why this works.

func WithKey

func WithKey(alg jwa.KeyAlgorithm, key interface{}, options ...WithKeySuboption) SignVerifyOption

WithKey is used to pass a static algorithm/key pair to either `jws.Sign()` or `jws.Verify()`.

The `alg` parameter is the identifier for the signature algorithm that should be used. It is of type `jwa.KeyAlgorithm` but in reality you can only pass `jwa.SignatureAlgorithm` types. It is this way so that the value in `(jwk.Key).Algorithm()` can be directly passed to the option. If you specify other algorithm types such as `jwa.KeyEncryptionAlgorithm`, then you will get an error when `jws.Sign()` or `jws.Verify()` is executed.

The `alg` parameter cannot be "none" (jwa.NoSignature) for security reasons. You will have to use a separate, more explicit option to allow the use of "none" algorithm (WithInsecureNoSignature).

The algorithm specified in the `alg` parameter MUST be able to support the type of key you provided, otherwise an error is returned.

Any of the following is accepted for the `key` parameter: * A "raw" key (e.g. rsa.PrivateKey, ecdsa.PrivateKey, etc) * A crypto.Signer * A jwk.Key

Note that due to technical reasons, this library is NOT able to differentiate between a valid/invalid key for given algorithm if the key implements crypto.Signer and the key is from an external library. For example, while we can tell that it is invalid to use `jwk.WithKey(jwa.RSA256, ecdsaPrivateKey)` because the key is presumably from `crypto/ecdsa` or this library, if you use a KMS wrapper that implements crypto.Signer that is outside of the go standard library or this library, we will not be able to properly catch the misuse of such keys -- the output will happily generate an ECDSA signature even in the presence of `jwa.RSA256`

A `crypto.Signer` is used when the private part of a key is kept in an inaccessible location, such as hardware. `crypto.Signer` is currently supported for RSA, ECDSA, and EdDSA family of algorithms. You may consider using `github.com/jwx-go/crypto-signer` if you would like to use keys stored in GCP/AWS KMS services.

If the key is a jwk.Key and the key contains a key ID (`kid` field), then it is added to the protected header generated by the signature.

`jws.WithKey()` can further accept suboptions to change signing behavior when used with `jws.Sign()`. `jws.WithProtected()` and `jws.WithPublic()` can be passed to specify JWS headers that should be used whe signing.

If the protected headers contain "b64" field, then the boolean value for the field is respected when serializing. That is, if you specify a header with `{"b64": false}`, then the payload is not base64 encoded.

These suboptions are ignored when the `jws.WithKey()` option is used with `jws.Verify()`.

func WithValidateKey

func WithValidateKey(v bool) SignVerifyOption

WithValidateKey specifies whether the key used for signing or verification should be validated before using. Note that this means calling `key.Validate()` on the key, which in turn means that your key must be a `jwk.Key` instance, or a key that can be converted to a `jwk.Key` by calling `jwk.Import()`. This means that your custom hardware-backed keys will probably not work.

You can directly call `key.Validate()` yourself if you need to mix keys that cannot be converted to `jwk.Key`.

Please also note that use of this option will also result in one extra conversion of raw keys to a `jwk.Key` instance. If you care about shaving off as much as possible, consider using a pre-validated key instead of using this option to validate the key on-demand each time.

By default, the key is not validated.

type SignVerifyParseOption

type SignVerifyParseOption interface {
	Option
	// contains filtered or unexported methods
}

func WithCompact

func WithCompact() SignVerifyParseOption

WithCompact specifies that the result of `jws.Sign()` is serialized in compact format.

By default `jws.Sign()` will opt to use compact format, so you usually do not need to specify this option other than to be explicit about it

func WithJSON

func WithJSON(options ...WithJSONSuboption) SignVerifyParseOption

WithJSON specifies that the result of `jws.Sign()` is serialized in JSON format.

If you pass multiple keys to `jws.Sign()`, it will fail unless you also pass this option.

type Signature

type Signature struct {
	// contains filtered or unexported fields
}

func NewSignature

func NewSignature() *Signature

func (*Signature) DecodeCtx

func (s *Signature) DecodeCtx() DecodeCtx

func (Signature) ProtectedHeaders

func (s Signature) ProtectedHeaders() Headers

func (Signature) PublicHeaders

func (s Signature) PublicHeaders() Headers

func (*Signature) SetDecodeCtx

func (s *Signature) SetDecodeCtx(dc DecodeCtx)

func (*Signature) SetProtectedHeaders

func (s *Signature) SetProtectedHeaders(v Headers) *Signature

func (*Signature) SetPublicHeaders

func (s *Signature) SetPublicHeaders(v Headers) *Signature

func (*Signature) SetSignature

func (s *Signature) SetSignature(v []byte) *Signature

func (*Signature) Sign

func (s *Signature) Sign(payload []byte, signer Signer, key interface{}) ([]byte, []byte, error)

Sign populates the signature field, with a signature generated by given the signer object and payload.

The first return value is the raw signature in binary format. The second return value s the full three-segment signature (e.g. "eyXXXX.XXXXX.XXXX")

func (Signature) Signature

func (s Signature) Signature() []byte

func (*Signature) UnmarshalJSON

func (s *Signature) UnmarshalJSON(data []byte) error

type Signer

type Signer interface {
	// Sign creates a signature for the given payload.
	// The second argument 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([]byte, interface{}) ([]byte, error)

	Algorithm() jwa.SignatureAlgorithm
}

Signer generates the signature for a given payload.

func NewSigner

func NewSigner(alg jwa.SignatureAlgorithm) (Signer, error)

NewSigner creates a signer that signs payloads using the given signature algorithm.

type SignerFactory

type SignerFactory interface {
	Create() (Signer, error)
}

type SignerFactoryFn

type SignerFactoryFn func() (Signer, error)

func (SignerFactoryFn) Create

func (fn SignerFactoryFn) Create() (Signer, error)

type Verifier

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
}

func NewVerifier

func NewVerifier(alg jwa.SignatureAlgorithm) (Verifier, error)

NewVerifier creates a verifier that signs payloads using the given signature algorithm.

type VerifierFactory

type VerifierFactory interface {
	Create() (Verifier, error)
}

type VerifierFactoryFn

type VerifierFactoryFn func() (Verifier, error)

func (VerifierFactoryFn) Create

func (fn VerifierFactoryFn) Create() (Verifier, error)

type VerifyOption

type VerifyOption interface {
	Option
	// contains filtered or unexported methods
}

VerifyOption describes options that can be passed to `jws.Verify`

func WithContext

func WithContext(v context.Context) VerifyOption

func WithKeyProvider

func WithKeyProvider(v KeyProvider) VerifyOption

func WithKeySet

func WithKeySet(set jwk.Set, options ...WithKeySetSuboption) VerifyOption

WithKeySet specifies a JWKS (jwk.Set) to use for verification.

Because a JWKS can contain multiple keys and this library cannot tell which one of the keys should be used for verification, we by default require that both `alg` and `kid` fields in the JWS _and_ the key match before a key is considered to be used.

There are ways to override this behavior, but they must be explicitly specified by the caller.

To work with keys/JWS messages not having a `kid` field, you may specify the suboption `WithKeySetRequired` via `jws.WithKey(key, jws.WithRequireKid(false))`. This will allow the library to proceed without having to match the `kid` field.

However, it will still check if the `alg` fields in the JWS message and the key(s) match. If you must work with JWS messages that do not have an `alg` field, you will need to use `jws.WithKeySet(key, jws.WithInferAlgorithm(true))`.

See the documentation for `WithInferAlgorithm()` for more details.

func WithKeyUsed

func WithKeyUsed(v interface{}) VerifyOption

WithKeyUsed allows you to specify the `jws.Verify()` function to return the key used for verification. This may be useful when you specify multiple key sources or if you pass a `jwk.Set` and you want to know which key was successful at verifying the signature.

`v` must be a pointer to an empty `interface{}`. Do not use `jwk.Key` here unless you are 100% sure that all keys that you have provided are instances of `jwk.Key` (remember that the jwx API allows users to specify a raw key such as *rsa.PublicKey)

func WithMessage

func WithMessage(v *Message) VerifyOption

WithMessage can be passed to Verify() to obtain the jws.Message upon a successful verification.

func WithVerifyAuto

func WithVerifyAuto(f jwk.Fetcher, options ...jwk.FetchOption) VerifyOption

WithVerifyAuto enables automatic verification of the signature using the JWKS specified in the `jku` header. Note that by default this option will _reject_ any jku provided by the JWS message. Read on for details.

The JWKS is retrieved by the `jwk.Fetcher` specified in the first argument. If the fetcher object is nil, the default fetcher, which is the `jwk.Fetch()` function (wrapped in the `jwk.FetchFunc` type) is used.

The remaining arguments are passed to the `(jwk.Fetcher).Fetch` method when the JWKS is retrieved.

jws.WithVerifyAuto(nil) // uses jwk.Fetch
jws.WithVerifyAuto(jwk.NewCachedFetcher(...)) // uses cached fetcher
jws.WithVerifyAuto(myFetcher) // use your custom fetcher

By default a whitelist that disallows all URLs is added to the options passed to the fetcher. You must explicitly specify a whitelist that allows the URLs you trust. This default behavior is provided because by design of the JWS specification it is the/ caller's responsibility to verify if the URL specified in the `jku` header can be trusted -- thus by default we trust nothing.

Users are free to specify an open whitelist if they so choose, but this must be explicitly done:

jws.WithVerifyAuto(nil, jwk.WithFetchWhitelist(jwk.InsecureWhitelist()))

You can also use `jwk.CachedFetcher` to use cached JWKS objects, but do note that this object is not really designed to accommodate a large set of arbitrary URLs. Use `jwk.CachedFetcher` as the first argument if you only have a small set of URLs that you trust. For anything more complex, you should implement your own `jwk.Fetcher` object.

type WithJSONSuboption

type WithJSONSuboption interface {
	Option
	// contains filtered or unexported methods
}

JSONSuboption describes suboptions that can be passed to the `jws.WithJSON()` option.

func WithPretty

func WithPretty(v bool) WithJSONSuboption

WithPretty specifies whether the JSON output should be formatted and indented

type WithKeySetSuboption

type WithKeySetSuboption interface {
	Option
	// contains filtered or unexported methods
}

WithKeySetSuboption is a suboption passed to the `jws.WithKeySet()` option

func WithInferAlgorithmFromKey

func WithInferAlgorithmFromKey(v bool) WithKeySetSuboption

WithInferAlgorithmFromKey specifies whether the JWS signing algorithm name should be inferred by looking at the provided key, in case the JWS message or the key does not have a proper `alg` header.

When this option is set to true, a list of algorithm(s) that is compatible with the key type will be enumerated, and _ALL_ of them will be tried against the key/message pair. If any of them succeeds, the verification will be considered successful.

Compared to providing explicit `alg` from the key this is slower, and verification may fail to verify if somehow our heuristics are wrong or outdated.

Also, automatic detection of signature verification methods are always more vulnerable for potential attack vectors.

It is highly recommended that you fix your key to contain a proper `alg` header field instead of resorting to using this option, but sometimes it just needs to happen.

func WithMultipleKeysPerKeyID

func WithMultipleKeysPerKeyID(v bool) WithKeySetSuboption

WithMultipleKeysPerKeyID specifies if we should expect multiple keys to match against a key ID. By default it is assumed that key IDs are unique, i.e. for a given key ID, the key set only contains a single key that has the matching ID. When this option is set to true, multiple keys that match the same key ID in the set can be tried.

func WithRequireKid

func WithRequireKid(v bool) WithKeySetSuboption

WithRequiredKid specifies whether the keys in the jwk.Set should only be matched if the target JWS message's Key ID and the Key ID in the given key matches.

func WithUseDefault

func WithUseDefault(v bool) WithKeySetSuboption

WithUseDefault specifies that if and only if a jwk.Key contains exactly one jwk.Key, that key should be used.

type WithKeySuboption

type WithKeySuboption interface {
	Option
	// contains filtered or unexported methods
}

WithKeySuboption describes option types that can be passed to the `jws.WithKey()` option.

func WithProtectedHeaders

func WithProtectedHeaders(v Headers) WithKeySuboption

WithProtected is used with `jws.WithKey()` option when used with `jws.Sign()` to specify a protected header to be attached to the JWS signature.

It has no effect if used when `jws.WithKey()` is passed to `jws.Verify()`

func WithPublicHeaders

func WithPublicHeaders(v Headers) WithKeySuboption

WithPublic is used with `jws.WithKey()` option when used with `jws.Sign()` to specify a public header to be attached to the JWS signature.

It has no effect if used when `jws.WithKey()` is passed to `jws.Verify()`

`jws.Sign()` will result in an error if `jws.WithPublic()` is used and the serialization format is compact serialization.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL