jws

package
v1.2.6 Latest Latest
Warning

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

Go to latest
Published: Aug 24, 2021 License: MIT Imports: 30 Imported by: 160

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/jwk
  • Add arbitrary fields in the JWS object
  • Ability to add/replace existing signature methods
  • Respect "b64" settings for RFC7797

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/jwa"
  "github.com/lestrrat-go/jwx/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"), 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, jwa.RS256, &privkey.PublicKey)
  if err != nil {
    log.Printf("failed to verify message: %s", err)
    return
  }

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

Programatically 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, algorithm, key)
jws.Verify(encodedjws, 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 RegisterCustomField added in v1.1.2

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 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 RegisterSigner added in v1.1.0

func RegisterSigner(alg jwa.SignatureAlgorithm, f SignerFactory)

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

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()`)

func RegisterVerifier added in v1.1.0

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()`)

func Sign

func Sign(payload []byte, alg jwa.SignatureAlgorithm, key interface{}, options ...SignOption) ([]byte, error)

Sign generates a signature for the given payload, and serializes it in compact serialization format. In this format you may NOT use multiple signers.

The `alg` parameter is the identifier for the signature algorithm that should be used.

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

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.

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

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

If you would like to pass custom headers, use the WithHeaders option.

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

func SignMulti

func SignMulti(payload []byte, options ...Option) ([]byte, error)

SignMulti accepts multiple signers via the options parameter, and creates a JWS in JSON serialization format that contains signatures from applying aforementioned signers.

Use `jws.WithSigner(...)` to specify values how to generate each signature in the `"signatures": [ ... ]` field.

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.

func SplitCompactReader added in v1.1.0

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

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

func SplitCompactString added in v1.1.0

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

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

func Verify

func Verify(buf []byte, alg jwa.SignatureAlgorithm, key interface{}, 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.

func VerifySet added in v1.1.0

func VerifySet(buf []byte, set jwk.Set) ([]byte, error)

VerifySet uses keys store in a jwk.Set to verify the payload in `buf`.

In order for `VerifySet()` to use a key in the given set, the `jwk.Key` object must have a valid "alg" field, and it also must have either an empty value or the value "sig" in the "use" field.

Furthermore if the JWS signature asks for a spefici "kid", the `jwk.Key` must have the same "kid" as the signature.

Types

type HMACSigner added in v1.1.0

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

HMACSigner uses crypto/hmac to sign the payloads.

func (HMACSigner) Algorithm added in v1.1.0

func (s HMACSigner) Algorithm() jwa.SignatureAlgorithm

func (HMACSigner) Sign added in v1.1.0

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

type HMACVerifier added in v1.1.0

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

func (HMACVerifier) Verify added in v1.1.0

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

type HeaderPair added in v1.0.0

type HeaderPair = mapiter.Pair

type Headers

type Headers interface {
	json.Marshaler
	json.Unmarshaler
	Algorithm() jwa.SignatureAlgorithm
	ContentType() string
	Critical() []string
	JWK() jwk.Key
	JWKSetURL() string
	KeyID() string
	Type() string
	X509CertChain() []string
	X509CertThumbprint() string
	X509CertThumbprintS256() string
	X509URL() string
	Iterate(ctx context.Context) Iterator
	Walk(context.Context, Visitor) error
	AsMap(context.Context) (map[string]interface{}, error)
	Copy(context.Context, Headers) error
	Merge(context.Context, Headers) (Headers, error)
	Get(string) (interface{}, bool)
	Set(string, interface{}) error
	Remove(string) error

	// PrivateParams returns the non-standard elements in the source structure
	// WARNING: DO NOT USE PrivateParams() IF YOU HAVE CONCURRENT CODE ACCESSING THEM.
	// Use AsMap() to get a copy of the entire header instead
	PrivateParams() map[string]interface{}
}

Headers describe a standard Header set.

func NewHeaders added in v1.0.0

func NewHeaders() Headers

type Iterator added in v1.0.0

type Iterator = mapiter.Iterator

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 added in v1.0.8

func NewMessage() *Message

func Parse

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

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 added in v1.1.0

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

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

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

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 ReadFile added in v1.1.0

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

func (*Message) AppendSignature added in v1.0.8

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

func (*Message) ClearSignatures added in v1.0.8

func (m *Message) ClearSignatures() *Message

func (Message) LookupSignature

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

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

func (Message) MarshalJSON added in v1.0.8

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

func (Message) Payload

func (m Message) Payload() []byte

Payload returns the decoded payload

func (*Message) SetPayload added in v1.0.8

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

func (Message) Signatures

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

func (*Message) UnmarshalJSON added in v1.0.8

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

type Option

type Option = option.Interface

func WithSigner

func WithSigner(signer Signer, key interface{}, public, protected Headers) Option

type ReadFileOption added in v1.1.0

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

ReadFileOption describes options that can be passed to ReadFile. Currently there are no options available that can be passed to ReadFile, but it is provided here for anticipated future additions

type SignOption added in v1.2.2

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

func WithHeaders

func WithHeaders(h Headers) SignOption

WithHeaders allows you to specify extra header values to include in the final JWS message

type Signature

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

func NewSignature added in v1.0.8

func NewSignature() *Signature

func (Signature) ProtectedHeaders

func (s Signature) ProtectedHeaders() Headers

func (Signature) PublicHeaders

func (s Signature) PublicHeaders() Headers

func (*Signature) SetProtectedHeaders added in v1.0.8

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

func (*Signature) SetPublicHeaders added in v1.0.8

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

func (*Signature) SetSignature added in v1.0.8

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

func (*Signature) Sign added in v1.1.0

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

type Signer added in v1.1.0

type Signer interface {
	// Sign creates a signature for the given payload.
	// The scond 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 added in v1.1.0

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

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

type SignerFactory added in v1.1.0

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

type SignerFactoryFn added in v1.1.0

type SignerFactoryFn func() (Signer, error)

func (SignerFactoryFn) Create added in v1.1.0

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

type Verifier added in v1.1.0

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 added in v1.1.0

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

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

type VerifierFactory added in v1.1.0

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

type VerifierFactoryFn added in v1.1.0

type VerifierFactoryFn func() (Verifier, error)

func (VerifierFactoryFn) Create added in v1.1.0

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

type VerifyOption added in v1.2.2

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

VerifyOption describes an option that can be passed to the jws.Verify function

func WithDetachedPayload added in v1.2.5

func WithDetachedPayload(v []byte) VerifyOption

WithDetachedPayload can be used to verify a JWS message with a detached payload. If you have to verify using this option, you should know exactly how and why this works.

func WithMessage added in v1.2.2

func WithMessage(m *Message) VerifyOption

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

type Visitor added in v1.0.0

type Visitor = iter.MapVisitor

type VisitorFunc added in v1.0.0

type VisitorFunc = iter.MapVisitorFunc

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

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