jwe

package
v2.0.9 Latest Latest
Warning

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

Go to latest
Published: Mar 21, 2023 License: MIT Imports: 34 Imported by: 29

README

JWE Go Reference

Package jwe implements JWE as described in RFC7516

  • Encrypt and Decrypt arbitrary data
  • Content compression and decompression
  • Add arbitrary fields in the JWE header object

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

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

Supported key encryption algorithm:

Algorithm Supported? Constant in jwa
RSA-PKCS1v1.5 YES jwa.RSA1_5
RSA-OAEP-SHA1 YES jwa.RSA_OAEP
RSA-OAEP-SHA256 YES jwa.RSA_OAEP_256
AES key wrap (128) YES jwa.A128KW
AES key wrap (192) YES jwa.A192KW
AES key wrap (256) YES jwa.A256KW
Direct encryption YES (1) jwa.DIRECT
ECDH-ES YES (1) jwa.ECDH_ES
ECDH-ES + AES key wrap (128) YES jwa.ECDH_ES_A128KW
ECDH-ES + AES key wrap (192) YES jwa.ECDH_ES_A192KW
ECDH-ES + AES key wrap (256) YES jwa.ECDH_ES_A256KW
AES-GCM key wrap (128) YES jwa.A128GCMKW
AES-GCM key wrap (192) YES jwa.A192GCMKW
AES-GCM key wrap (256) YES jwa.A256GCMKW
PBES2 + HMAC-SHA256 + AES key wrap (128) YES jwa.PBES2_HS256_A128KW
PBES2 + HMAC-SHA384 + AES key wrap (192) YES jwa.PBES2_HS384_A192KW
PBES2 + HMAC-SHA512 + AES key wrap (256) YES jwa.PBES2_HS512_A256KW
  • Note 1: Single-recipient only

Supported content encryption algorithm:

Algorithm Supported? Constant in jwa
AES-CBC + HMAC-SHA256 (128) YES jwa.A128CBC_HS256
AES-CBC + HMAC-SHA384 (192) YES jwa.A192CBC_HS384
AES-CBC + HMAC-SHA512 (256) YES jwa.A256CBC_HS512
AES-GCM (128) YES jwa.A128GCM
AES-GCM (192) YES jwa.A192GCM
AES-GCM (256) YES jwa.A256GCM

SYNOPSIS

Encrypt data

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

  payload := []byte("Lorem Ipsum")

  encrypted, err := jwe.Encrypt(payload, jwe.WithKey(jwa.RSA1_5, &privkey.PublicKey), jwe.WithContentEncryption(jwa.A128CBC_HS256))
  if err != nil {
    log.Printf("failed to encrypt payload: %s", err)
    return
  }
  _ = encrypted
  // OUTPUT:
}

Decrypt data

func ExampleDecrypt() {
  privkey, encrypted, err := exampleGenPayload()
  if err != nil {
    log.Printf("failed to generate encrypted payload: %s", err)
    return
  }

  decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA1_5, privkey))
  if err != nil {
    log.Printf("failed to decrypt: %s", err)
    return
  }

  if string(decrypted) != "Lorem Ipsum" {
    log.Printf("WHAT?!")
    return
  }
  // OUTPUT:
}

Documentation

Overview

Package jwe implements JWE as described in https://tools.ietf.org/html/rfc7516

Index

Constants

View Source
const (
	AgreementPartyUInfoKey    = "apu"
	AgreementPartyVInfoKey    = "apv"
	AlgorithmKey              = "alg"
	CompressionKey            = "zip"
	ContentEncryptionKey      = "enc"
	ContentTypeKey            = "cty"
	CriticalKey               = "crit"
	EphemeralPublicKeyKey     = "epk"
	JWKKey                    = "jwk"
	JWKSetURLKey              = "jku"
	KeyIDKey                  = "kid"
	TypeKey                   = "typ"
	X509CertChainKey          = "x5c"
	X509CertThumbprintKey     = "x5t"
	X509CertThumbprintS256Key = "x5t#S256"
	X509URLKey                = "x5u"
)
View Source
const (
	AuthenticatedDataKey    = "aad"
	CipherTextKey           = "ciphertext"
	CountKey                = "p2c"
	InitializationVectorKey = "iv"
	ProtectedHeadersKey     = "protected"
	RecipientsKey           = "recipients"
	SaltKey                 = "p2s"
	TagKey                  = "tag"
	UnprotectedHeadersKey   = "unprotected"
	HeadersKey              = "header"
	EncryptedKeyKey         = "encrypted_key"
)

Variables

This section is empty.

Functions

func Compact

func Compact(m *Message, _ ...CompactOption) ([]byte, error)

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

This function currently does not take any options, but the function signature contains `options` for possible future expansion of the API

func Decrypt

func Decrypt(buf []byte, options ...DecryptOption) ([]byte, error)

Decrypt takes the key encryption algorithm and the corresponding key to decrypt the JWE message, and returns the decrypted payload. The JWE message can be either compact or full JSON format.

`alg` accepts a `jwa.KeyAlgorithm` for convenience so you can directly pass the result of `(jwk.Key).Algorithm()`, but in practice it must be of type `jwa.KeyEncryptionAlgorithm` or otherwise it will cause an error.

`key` must be a private key. It can be either in its raw format (e.g. *rsa.PrivateKey) or a jwk.Key

func Encrypt

func Encrypt(payload []byte, options ...EncryptOption) ([]byte, error)

Encrypt generates a JWE 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 `jwe.Encrypt()` by using `jwe.WithKey()` option.

jwe.Encrypt(payload, jwe.WithKey(alg, key))
jwe.Encrypt(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 recipients, and users must specifically ask for the JSON serialization format.

Read the documentation for `jwe.WithKey()` to learn more about the possible values that can be used for `alg` and `key`.

Look for options that return `jwe.EncryptOption` or `jws.EncryptDecryptOption` for a complete list of options that can be passed to this function.

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

Types

type CompactOption

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

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

type DecryptOption

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

DecryptOption describes options that can be passed to `jwe.Decrypt`

func WithKeyProvider

func WithKeyProvider(v KeyProvider) DecryptOption

func WithKeySet

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

func WithKeyUsed

func WithKeyUsed(v interface{}) DecryptOption

WithKeyUsed allows you to specify the `jwe.Decrypt()` function to return the key used for decryption. 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 decrypting 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) DecryptOption

WithMessage provides a message object to be populated by `jwe.Decrpt` Using this option allows you to decrypt AND obtain the `jwe.Message` in one go.

Note that you should NOT be using the message object for anything other than inspecting its contents. Particularly, do not expect the message reliable when you call `Decrypt` on it. `(jwe.Message).Decrypt` is slated to be deprecated in the next major version.

type EncryptDecryptOption

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

EncryptDecryptOption describes options that can be passed to either `jwe.Encrypt` or `jwe.Decrypt`

func WithKey

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

WithKey is used to pass a static algorithm/key pair to either `jwe.Encrypt()` or `jwe.Decrypt()`. either a raw key or `jwk.Key` may be passed as `key`.

The `alg` parameter is the identifier for the key encryption 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.ContentEncryptionAlgorithm`, then you will get an error when `jwe.Encrypt()` or `jwe.Decrypt()` is executed.

Unlike `jwe.WithKeySet()`, the `kid` field does not need to match for the key to be tried.

type EncryptOption

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

EncryptOption describes options that can be passed to `jwe.Encrypt`

func WithCompact

func WithCompact() EncryptOption

WithCompact specifies that the result of `jwe.Encrypt()` is serialized in compact format.

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

func WithCompress

func WithCompress(v jwa.CompressionAlgorithm) EncryptOption

WithCompress specifies the compression algorithm to use when encrypting a payload using `jwe.Encrypt` (Yes, we know it can only be "" or "DEF", but the way the specification is written it could allow for more options, and therefore this option takes an argument)

func WithContentEncryption

func WithContentEncryption(v jwa.ContentEncryptionAlgorithm) EncryptOption

WithContentEncryptionAlgorithm specifies the algorithm to encrypt the JWE message content with. If not provided, `jwa.A256GCM` is used.

func WithJSON

func WithJSON(options ...WithJSONSuboption) EncryptOption

WithJSON specifies that the result of `jwe.Encrypt()` is serialized in JSON format.

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

func WithMergeProtectedHeaders

func WithMergeProtectedHeaders(v bool) EncryptOption

WithMergeProtectedHeaders specify that when given multiple headers as options to `jwe.Encrypt`, these headers should be merged instead of overwritten

func WithProtectedHeaders

func WithProtectedHeaders(h Headers) EncryptOption

Specify contents of the protected header. Some fields such as "enc" and "zip" will be overwritten when encryption is performed.

There is no equivalent for unprotected headers in this implementation

type HeaderPair

type HeaderPair = mapiter.Pair

type Headers

type Headers interface {
	json.Marshaler
	json.Unmarshaler
	AgreementPartyUInfo() []byte
	AgreementPartyVInfo() []byte
	Algorithm() jwa.KeyEncryptionAlgorithm
	Compression() jwa.CompressionAlgorithm
	ContentEncryption() jwa.ContentEncryptionAlgorithm
	ContentType() string
	Critical() []string
	EphemeralPublicKey() jwk.Key
	JWK() jwk.Key
	JWKSetURL() string
	KeyID() string
	Type() string
	X509CertChain() *cert.Chain
	X509CertThumbprint() string
	X509CertThumbprintS256() string
	X509URL() string
	Iterate(ctx context.Context) Iterator
	Walk(ctx context.Context, v Visitor) error
	AsMap(ctx context.Context) (map[string]interface{}, error)
	Get(string) (interface{}, bool)
	Set(string, interface{}) error
	Remove(string) error
	Encode() ([]byte, error)
	Decode([]byte) error
	// PrivateParams returns the map containing the non-standard ('private') parameters
	// in the associated header. 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{}
	Clone(context.Context) (Headers, error)
	Copy(context.Context, Headers) error
	Merge(context.Context, Headers) (Headers, error)
}

Headers describe a standard Header set.

func NewHeaders

func NewHeaders() Headers

type Iterator

type Iterator = mapiter.Iterator

type KeyProvider

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

KeyProvider is responsible for providing key(s) to encrypt or decrypt a payload. Multiple `jwe.KeyProvider`s can be passed to `jwe.Encrypt()` or `jwe.Decrypt()`

`jwe.Encrypt()` can only accept static key providers via `jwe.WithKey()`, while `jwe.Derypt()` can accept `jwe.WithKey()`, `jwe.WithKeySet()`, and `jwe.WithKeyProvider()`.

Understanding how this works is crucial to learn how this package works. Here we will use `jwe.Decrypt()` as an example to show how the `KeyProvider` works.

`jwe.Encrypt()` is straightforward: the content encryption key is encrypted using the provided keys, and JWS recipient objects are created for each.

`jwe.Decrypt()` 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 decryption.

The first thing that `jwe.Decrypt()` needs to do is to collect the KeyProviders from the option list that the user provided (presented in pseudocode):

keyProviders := filterKeyProviders(options)

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

for r in msg.Recipients {
  for kp in keyProviders {
    kp.FetcKeys(ctx, sink, r, 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 `jwe.WithKey()` sends the same key, `jwe.WithKeySet()` sends keys that matches a particular `kid` and `alg`, and finally `jwe.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 decryptJWEKey(recipient.EncryptedKey(), key) {
    return OK
  }
}

type KeyProviderFunc

type KeyProviderFunc func(context.Context, KeySink, Recipient, *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, r Recipient, msg *Message) error

type KeySink

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

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

type Message

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

Message contains the entire encrypted JWE message. You should not expect to use Message for anything other than inspecting the state of an encrypted message. This is because encryption is highly context sensitive, and once we parse the original payload into an object, we may not always be able to recreate the exact context in which the encryption happened.

For example, it is totally valid for if the protected header's integrity was calculated using a non-standard line breaks:

{"a dummy":
  "protected header"}

Once parsed, though, we can only serialize the protected header as:

{"a dummy":"protected header"}

which would obviously result in a contradicting integrity value if we tried to re-calculate it from a parsed message.

func NewMessage

func NewMessage() *Message

NewMessage creates a new message

func Parse

func Parse(buf []byte, _ ...ParseOption) (*Message, error)

Parse parses the JWE message into a Message object. The JWE message can be either compact or full JSON format.

Parse() currently does not take any options, but the API accepts it in anticipation of future addition.

func ParseReader

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

ParseReader is the same as Parse, but takes an io.Reader.

func ParseString

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

ParseString is the same as Parse, but takes a string.

func ReadFile

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

func (*Message) AuthenticatedData

func (m *Message) AuthenticatedData() []byte

func (*Message) CipherText

func (m *Message) CipherText() []byte

func (*Message) InitializationVector

func (m *Message) InitializationVector() []byte

func (*Message) MarshalJSON

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

func (*Message) ProtectedHeaders

func (m *Message) ProtectedHeaders() Headers

func (*Message) Recipients

func (m *Message) Recipients() []Recipient

func (*Message) Set

func (m *Message) Set(k string, v interface{}) error

func (*Message) Tag

func (m *Message) Tag() []byte

func (*Message) UnmarshalJSON

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

func (*Message) UnprotectedHeaders

func (m *Message) UnprotectedHeaders() Headers

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 `jwe.ReadFile`

func WithFS

func WithFS(v fs.FS) ReadFileOption

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

type Recipient

type Recipient interface {
	Headers() Headers
	EncryptedKey() []byte
	SetHeaders(Headers) error
	SetEncryptedKey([]byte) error
}

Recipient holds the encrypted key and hints to decrypt the key

func NewRecipient

func NewRecipient() Recipient

NewRecipient creates a Recipient object

type Visitor

type Visitor = iter.MapVisitor

type VisitorFunc

type VisitorFunc = iter.MapVisitorFunc

type WithJSONSuboption

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

JSONSuboption describes suboptions that can be passed to `jwe.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 WithKeySet() option

func WithRequireKid

func WithRequireKid(v bool) WithKeySetSuboption

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

type WithKeySuboption

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

func WithPerRecipientHeaders

func WithPerRecipientHeaders(hdr Headers) WithKeySuboption

WithPerRecipientHeaders is used to pass header values for each recipient. Note that these headers are by definition _unprotected_.

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

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