Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+70
View File
@@ -0,0 +1,70 @@
load("@rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "jwe",
srcs = [
"compress.go",
"decrypt.go",
"encrypt.go",
"errors.go",
"filter.go",
"headers.go",
"headers_gen.go",
"interface.go",
"io.go",
"jwe.go",
"key_provider.go",
"message.go",
"options.go",
"options_gen.go",
],
importpath = "github.com/lestrrat-go/jwx/v3/jwe",
visibility = ["//visibility:public"],
deps = [
"//cert",
"//internal/base64",
"//transform",
"//internal/json",
"//internal/tokens",
"//internal/keyconv",
"//internal/pool",
"//jwa",
"//jwe/internal/aescbc",
"//jwe/internal/cipher",
"//jwe/internal/content_crypt",
"//jwe/internal/keygen",
"//jwe/jwebb",
"//jwk",
"@com_github_lestrrat_go_blackmagic//:blackmagic",
"@com_github_lestrrat_go_option_v2//:option",
"@org_golang_x_crypto//pbkdf2",
],
)
go_test(
name = "jwe_test",
srcs = [
"filter_test.go",
"gh402_test.go",
"headers_test.go",
"jwe_test.go",
"message_test.go",
"options_gen_test.go",
"speed_test.go",
],
embed = [":jwe"],
deps = [
"//cert",
"//internal/json",
"//internal/jwxtest",
"//jwa",
"//jwk",
"@com_github_stretchr_testify//require",
],
)
alias(
name = "go_default_library",
actual = ":jwe",
visibility = ["//visibility:public"],
)
+94
View File
@@ -0,0 +1,94 @@
# JWE [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/jwx/v3/jwe.svg)](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwe)
Package jwe implements JWE as described in [RFC7516](https://tools.ietf.org/html/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](../docs).
Examples are located in the examples directory ([jwe_example_test.go](../examples/jwe_example_test.go))
Supported key encryption algorithm:
| Algorithm | Supported? | Constant in [jwa](../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](../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
```go
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
```go
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:
}
```
+62
View File
@@ -0,0 +1,62 @@
package jwe
import (
"bytes"
"compress/flate"
"fmt"
"io"
"github.com/lestrrat-go/jwx/v3/internal/pool"
)
func uncompress(src []byte, maxBufferSize int64) ([]byte, error) {
var dst bytes.Buffer
r := flate.NewReader(bytes.NewReader(src))
defer r.Close()
var buf [16384]byte
var sofar int64
for {
n, readErr := r.Read(buf[:])
sofar += int64(n)
if sofar > maxBufferSize {
return nil, fmt.Errorf(`compressed payload exceeds maximum allowed size`)
}
if readErr != nil {
// if we have a read error, and it's not EOF, then we need to stop
if readErr != io.EOF {
return nil, fmt.Errorf(`failed to read inflated data: %w`, readErr)
}
}
if _, err := dst.Write(buf[:n]); err != nil {
return nil, fmt.Errorf(`failed to write inflated data: %w`, err)
}
if readErr != nil {
// if it got here, then readErr == io.EOF, we're done
return dst.Bytes(), nil
}
}
}
func compress(plaintext []byte) ([]byte, error) {
buf := pool.BytesBuffer().Get()
defer pool.BytesBuffer().Put(buf)
w, _ := flate.NewWriter(buf, 1)
in := plaintext
for len(in) > 0 {
n, err := w.Write(in)
if err != nil {
return nil, fmt.Errorf(`failed to write to compression writer: %w`, err)
}
in = in[n:]
}
if err := w.Close(); err != nil {
return nil, fmt.Errorf(`failed to close compression writer: %w`, err)
}
ret := make([]byte, buf.Len())
copy(ret, buf.Bytes())
return ret, nil
}
+227
View File
@@ -0,0 +1,227 @@
package jwe
import (
"fmt"
"github.com/lestrrat-go/jwx/v3/internal/tokens"
"github.com/lestrrat-go/jwx/v3/jwa"
"github.com/lestrrat-go/jwx/v3/jwe/internal/content_crypt"
"github.com/lestrrat-go/jwx/v3/jwe/jwebb"
)
// decrypter is responsible for taking various components to decrypt a message.
// its operation is not concurrency safe. You must provide locking yourself
//
//nolint:govet
type decrypter struct {
aad []byte
apu []byte
apv []byte
cek *[]byte
computedAad []byte
iv []byte
keyiv []byte
keysalt []byte
keytag []byte
tag []byte
privkey any
pubkey any
ctalg jwa.ContentEncryptionAlgorithm
keyalg jwa.KeyEncryptionAlgorithm
cipher content_crypt.Cipher
keycount int
}
// newDecrypter Creates a new Decrypter instance. You must supply the
// rest of parameters via their respective setter methods before
// calling Decrypt().
//
// privkey must be a private key in its "raw" format (i.e. something like
// *rsa.PrivateKey, instead of jwk.Key)
//
// You should consider this object immutable once you assign values to it.
func newDecrypter(keyalg jwa.KeyEncryptionAlgorithm, ctalg jwa.ContentEncryptionAlgorithm, privkey any) *decrypter {
return &decrypter{
ctalg: ctalg,
keyalg: keyalg,
privkey: privkey,
}
}
func (d *decrypter) AgreementPartyUInfo(apu []byte) *decrypter {
d.apu = apu
return d
}
func (d *decrypter) AgreementPartyVInfo(apv []byte) *decrypter {
d.apv = apv
return d
}
func (d *decrypter) AuthenticatedData(aad []byte) *decrypter {
d.aad = aad
return d
}
func (d *decrypter) ComputedAuthenticatedData(aad []byte) *decrypter {
d.computedAad = aad
return d
}
func (d *decrypter) ContentEncryptionAlgorithm(ctalg jwa.ContentEncryptionAlgorithm) *decrypter {
d.ctalg = ctalg
return d
}
func (d *decrypter) InitializationVector(iv []byte) *decrypter {
d.iv = iv
return d
}
func (d *decrypter) KeyCount(keycount int) *decrypter {
d.keycount = keycount
return d
}
func (d *decrypter) KeyInitializationVector(keyiv []byte) *decrypter {
d.keyiv = keyiv
return d
}
func (d *decrypter) KeySalt(keysalt []byte) *decrypter {
d.keysalt = keysalt
return d
}
func (d *decrypter) KeyTag(keytag []byte) *decrypter {
d.keytag = keytag
return d
}
// PublicKey sets the public key to be used in decoding EC based encryptions.
// The key must be in its "raw" format (i.e. *ecdsa.PublicKey, instead of jwk.Key)
func (d *decrypter) PublicKey(pubkey any) *decrypter {
d.pubkey = pubkey
return d
}
func (d *decrypter) Tag(tag []byte) *decrypter {
d.tag = tag
return d
}
func (d *decrypter) CEK(ptr *[]byte) *decrypter {
d.cek = ptr
return d
}
func (d *decrypter) ContentCipher() (content_crypt.Cipher, error) {
if d.cipher == nil {
cipher, err := jwebb.CreateContentCipher(d.ctalg.String())
if err != nil {
return nil, err
}
d.cipher = cipher
}
return d.cipher, nil
}
func (d *decrypter) Decrypt(recipient Recipient, ciphertext []byte, msg *Message) (plaintext []byte, err error) {
cek, keyerr := d.DecryptKey(recipient, msg)
if keyerr != nil {
err = fmt.Errorf(`failed to decrypt key: %w`, keyerr)
return
}
cipher, ciphererr := d.ContentCipher()
if ciphererr != nil {
err = fmt.Errorf(`failed to fetch content crypt cipher: %w`, ciphererr)
return
}
computedAad := d.computedAad
if d.aad != nil {
computedAad = append(append(computedAad, tokens.Period), d.aad...)
}
plaintext, err = cipher.Decrypt(cek, d.iv, ciphertext, d.tag, computedAad)
if err != nil {
err = fmt.Errorf(`failed to decrypt payload: %w`, err)
return
}
if d.cek != nil {
*d.cek = cek
}
return plaintext, nil
}
func (d *decrypter) DecryptKey(recipient Recipient, msg *Message) (cek []byte, err error) {
recipientKey := recipient.EncryptedKey()
if kd, ok := d.privkey.(KeyDecrypter); ok {
return kd.DecryptKey(d.keyalg, recipientKey, recipient, msg)
}
if jwebb.IsDirect(d.keyalg.String()) {
cek, ok := d.privkey.([]byte)
if !ok {
return nil, fmt.Errorf("decrypt key: []byte is required as the key for %s (got %T)", d.keyalg, d.privkey)
}
return jwebb.KeyDecryptDirect(recipientKey, recipientKey, d.keyalg.String(), cek)
}
if jwebb.IsPBES2(d.keyalg.String()) {
password, ok := d.privkey.([]byte)
if !ok {
return nil, fmt.Errorf("decrypt key: []byte is required as the password for %s (got %T)", d.keyalg, d.privkey)
}
salt := []byte(d.keyalg.String())
salt = append(salt, byte(0))
salt = append(salt, d.keysalt...)
return jwebb.KeyDecryptPBES2(recipientKey, recipientKey, d.keyalg.String(), password, salt, d.keycount)
}
if jwebb.IsAESGCMKW(d.keyalg.String()) {
sharedkey, ok := d.privkey.([]byte)
if !ok {
return nil, fmt.Errorf("decrypt key: []byte is required as the key for %s (got %T)", d.keyalg, d.privkey)
}
return jwebb.KeyDecryptAESGCMKW(recipientKey, recipientKey, d.keyalg.String(), sharedkey, d.keyiv, d.keytag)
}
if jwebb.IsECDHES(d.keyalg.String()) {
alg, keysize, keywrap, err := jwebb.KeyEncryptionECDHESKeySize(d.keyalg.String(), d.ctalg.String())
if err != nil {
return nil, fmt.Errorf(`failed to determine ECDH-ES key size: %w`, err)
}
if !keywrap {
return jwebb.KeyDecryptECDHES(recipientKey, cek, alg, d.apu, d.apv, d.privkey, d.pubkey, keysize)
}
return jwebb.KeyDecryptECDHESKeyWrap(recipientKey, recipientKey, d.keyalg.String(), d.apu, d.apv, d.privkey, d.pubkey, keysize)
}
if jwebb.IsRSA15(d.keyalg.String()) {
cipher, err := d.ContentCipher()
if err != nil {
return nil, fmt.Errorf(`failed to fetch content crypt cipher: %w`, err)
}
keysize := cipher.KeySize() / 2
return jwebb.KeyDecryptRSA15(recipientKey, recipientKey, d.privkey, keysize)
}
if jwebb.IsRSAOAEP(d.keyalg.String()) {
return jwebb.KeyDecryptRSAOAEP(recipientKey, recipientKey, d.keyalg.String(), d.privkey)
}
if jwebb.IsAESKW(d.keyalg.String()) {
sharedkey, ok := d.privkey.([]byte)
if !ok {
return nil, fmt.Errorf("[]byte is required as the key to decrypt %s", d.keyalg.String())
}
return jwebb.KeyDecryptAESKW(recipientKey, recipientKey, d.keyalg.String(), sharedkey)
}
return nil, fmt.Errorf(`unsupported algorithm for key decryption (%s)`, d.keyalg)
}
+193
View File
@@ -0,0 +1,193 @@
package jwe
import (
"crypto/ecdh"
"crypto/ecdsa"
"crypto/rsa"
"fmt"
"github.com/lestrrat-go/jwx/v3/internal/keyconv"
"github.com/lestrrat-go/jwx/v3/jwa"
"github.com/lestrrat-go/jwx/v3/jwe/internal/content_crypt"
"github.com/lestrrat-go/jwx/v3/jwe/internal/keygen"
"github.com/lestrrat-go/jwx/v3/jwe/jwebb"
)
// encrypter is responsible for taking various components to encrypt a key.
// its operation is not concurrency safe. You must provide locking yourself
//
//nolint:govet
type encrypter struct {
apu []byte
apv []byte
ctalg jwa.ContentEncryptionAlgorithm
keyalg jwa.KeyEncryptionAlgorithm
pubkey any
rawKey any
cipher content_crypt.Cipher
}
// newEncrypter creates a new Encrypter instance with all required parameters.
// The content cipher is built internally during construction.
//
// pubkey must be a public key in its "raw" format (i.e. something like
// *rsa.PublicKey, instead of jwk.Key)
//
// You should consider this object immutable once created.
func newEncrypter(keyalg jwa.KeyEncryptionAlgorithm, ctalg jwa.ContentEncryptionAlgorithm, pubkey any, rawKey any, apu, apv []byte) (*encrypter, error) {
cipher, err := jwebb.CreateContentCipher(ctalg.String())
if err != nil {
return nil, fmt.Errorf(`failed to create content cipher: %w`, err)
}
return &encrypter{
apu: apu,
apv: apv,
ctalg: ctalg,
keyalg: keyalg,
pubkey: pubkey,
rawKey: rawKey,
cipher: cipher,
}, nil
}
func (e *encrypter) EncryptKey(cek []byte) (keygen.ByteSource, error) {
if ke, ok := e.pubkey.(KeyEncrypter); ok {
encrypted, err := ke.EncryptKey(cek)
if err != nil {
return nil, err
}
return keygen.ByteKey(encrypted), nil
}
if jwebb.IsDirect(e.keyalg.String()) {
sharedkey, ok := e.rawKey.([]byte)
if !ok {
return nil, fmt.Errorf("encrypt key: []byte is required as the key for %s (got %T)", e.keyalg, e.rawKey)
}
return jwebb.KeyEncryptDirect(cek, e.keyalg.String(), sharedkey)
}
if jwebb.IsPBES2(e.keyalg.String()) {
password, ok := e.rawKey.([]byte)
if !ok {
return nil, fmt.Errorf("encrypt key: []byte is required as the password for %s (got %T)", e.keyalg, e.rawKey)
}
return jwebb.KeyEncryptPBES2(cek, e.keyalg.String(), password)
}
if jwebb.IsAESGCMKW(e.keyalg.String()) {
sharedkey, ok := e.rawKey.([]byte)
if !ok {
return nil, fmt.Errorf("encrypt key: []byte is required as the key for %s (got %T)", e.keyalg, e.rawKey)
}
return jwebb.KeyEncryptAESGCMKW(cek, e.keyalg.String(), sharedkey)
}
if jwebb.IsECDHES(e.keyalg.String()) {
_, keysize, keywrap, err := jwebb.KeyEncryptionECDHESKeySize(e.keyalg.String(), e.ctalg.String())
if err != nil {
return nil, fmt.Errorf(`failed to determine ECDH-ES key size: %w`, err)
}
// Use rawKey for ECDH-ES operations - it should contain the actual key material
keyToUse := e.rawKey
if keyToUse == nil {
keyToUse = e.pubkey
}
switch key := keyToUse.(type) {
case *ecdsa.PublicKey:
// no op
case ecdsa.PublicKey:
keyToUse = &key
case *ecdsa.PrivateKey:
keyToUse = &key.PublicKey
case ecdsa.PrivateKey:
keyToUse = &key.PublicKey
case *ecdh.PublicKey:
// no op
case ecdh.PublicKey:
keyToUse = &key
case ecdh.PrivateKey:
keyToUse = key.PublicKey()
case *ecdh.PrivateKey:
keyToUse = key.PublicKey()
}
// Determine key type and call appropriate function
switch key := keyToUse.(type) {
case *ecdh.PublicKey:
if key.Curve() == ecdh.X25519() {
if !keywrap {
return jwebb.KeyEncryptECDHESX25519(cek, e.keyalg.String(), e.apu, e.apv, key, keysize, e.ctalg.String())
}
return jwebb.KeyEncryptECDHESKeyWrapX25519(cek, e.keyalg.String(), e.apu, e.apv, key, keysize, e.ctalg.String())
}
var ecdsaKey *ecdsa.PublicKey
if err := keyconv.ECDHToECDSA(&ecdsaKey, key); err != nil {
return nil, fmt.Errorf(`encrypt: failed to convert ECDH public key to ECDSA: %w`, err)
}
keyToUse = ecdsaKey
}
switch key := keyToUse.(type) {
case *ecdsa.PublicKey:
if !keywrap {
return jwebb.KeyEncryptECDHESECDSA(cek, e.keyalg.String(), e.apu, e.apv, key, keysize, e.ctalg.String())
}
return jwebb.KeyEncryptECDHESKeyWrapECDSA(cek, e.keyalg.String(), e.apu, e.apv, key, keysize, e.ctalg.String())
default:
return nil, fmt.Errorf(`encrypt: unsupported key type for ECDH-ES: %T`, keyToUse)
}
}
if jwebb.IsRSA15(e.keyalg.String()) {
keyToUse := e.rawKey
if keyToUse == nil {
keyToUse = e.pubkey
}
// Handle rsa.PublicKey by value - convert to pointer
if pk, ok := keyToUse.(rsa.PublicKey); ok {
keyToUse = &pk
}
var pubkey *rsa.PublicKey
if err := keyconv.RSAPublicKey(&pubkey, keyToUse); err != nil {
return nil, fmt.Errorf(`encrypt: failed to convert to RSA public key: %w`, err)
}
return jwebb.KeyEncryptRSA15(cek, e.keyalg.String(), pubkey)
}
if jwebb.IsRSAOAEP(e.keyalg.String()) {
keyToUse := e.rawKey
if keyToUse == nil {
keyToUse = e.pubkey
}
// Handle rsa.PublicKey by value - convert to pointer
if pk, ok := keyToUse.(rsa.PublicKey); ok {
keyToUse = &pk
}
var pubkey *rsa.PublicKey
if err := keyconv.RSAPublicKey(&pubkey, keyToUse); err != nil {
return nil, fmt.Errorf(`encrypt: failed to convert to RSA public key: %w`, err)
}
return jwebb.KeyEncryptRSAOAEP(cek, e.keyalg.String(), pubkey)
}
if jwebb.IsAESKW(e.keyalg.String()) {
sharedkey, ok := e.rawKey.([]byte)
if !ok {
return nil, fmt.Errorf("[]byte is required as the key to encrypt %s", e.keyalg.String())
}
return jwebb.KeyEncryptAESKW(cek, e.keyalg.String(), sharedkey)
}
return nil, fmt.Errorf(`unsupported algorithm for key encryption (%s)`, e.keyalg)
}
+90
View File
@@ -0,0 +1,90 @@
package jwe
import "errors"
type encryptError struct {
error
}
func (e encryptError) Unwrap() error {
return e.error
}
func (encryptError) Is(err error) bool {
_, ok := err.(encryptError)
return ok
}
var errDefaultEncryptError = encryptError{errors.New(`encrypt error`)}
// EncryptError returns an error that can be passed to `errors.Is` to check if the error is an error returned by `jwe.Encrypt`.
func EncryptError() error {
return errDefaultEncryptError
}
type decryptError struct {
error
}
func (e decryptError) Unwrap() error {
return e.error
}
func (decryptError) Is(err error) bool {
_, ok := err.(decryptError)
return ok
}
var errDefaultDecryptError = decryptError{errors.New(`decrypt error`)}
// DecryptError returns an error that can be passed to `errors.Is` to check if the error is an error returned by `jwe.Decrypt`.
func DecryptError() error {
return errDefaultDecryptError
}
type recipientError struct {
error
}
func (e recipientError) Unwrap() error {
return e.error
}
func (recipientError) Is(err error) bool {
_, ok := err.(recipientError)
return ok
}
var errDefaultRecipientError = recipientError{errors.New(`recipient error`)}
// RecipientError returns an error that can be passed to `errors.Is` to check if the error is
// an error that occurred while attempting to decrypt a JWE message for a particular recipient.
//
// For example, if the JWE message failed to parse during `jwe.Decrypt`, it will be a
// `jwe.DecryptError`, but NOT `jwe.RecipientError`. However, if the JWE message could not
// be decrypted for any of the recipients, then it will be a `jwe.RecipientError`
// (actually, it will be _multiple_ `jwe.RecipientError` errors, one for each recipient)
func RecipientError() error {
return errDefaultRecipientError
}
type parseError struct {
error
}
func (e parseError) Unwrap() error {
return e.error
}
func (parseError) Is(err error) bool {
_, ok := err.(parseError)
return ok
}
var errDefaultParseError = parseError{errors.New(`parse error`)}
// ParseError returns an error that can be passed to `errors.Is` to check if the error
// is an error returned by `jwe.Parse` and related functions.
func ParseError() error {
return errDefaultParseError
}
+36
View File
@@ -0,0 +1,36 @@
package jwe
import (
"github.com/lestrrat-go/jwx/v3/transform"
)
// HeaderFilter is an interface that allows users to filter JWE header fields.
// It provides two methods: Filter and Reject; Filter returns a new header with only
// the fields that match the filter criteria, while Reject returns a new header with
// only the fields that DO NOT match the filter.
//
// EXPERIMENTAL: This API is experimental and its interface and behavior is
// subject to change in future releases. This API is not subject to semver
// compatibility guarantees.
type HeaderFilter interface {
Filter(header Headers) (Headers, error)
Reject(header Headers) (Headers, error)
}
// StandardHeadersFilter returns a HeaderFilter that filters out standard JWE header fields.
//
// You can use this filter to create headers that either only have standard fields
// or only custom fields.
//
// If you need to configure the filter more precisely, consider
// using the HeaderNameFilter directly.
func StandardHeadersFilter() HeaderFilter {
return stdHeadersFilter
}
var stdHeadersFilter = NewHeaderNameFilter(stdHeaderNames...)
// NewHeaderNameFilter creates a new HeaderNameFilter with the specified field names.
func NewHeaderNameFilter(names ...string) HeaderFilter {
return transform.NewNameBasedFilter[Headers](names...)
}
+95
View File
@@ -0,0 +1,95 @@
package jwe
import (
"fmt"
"github.com/lestrrat-go/jwx/v3/internal/base64"
"github.com/lestrrat-go/jwx/v3/internal/json"
)
type isZeroer interface {
isZero() bool
}
func (h *stdHeaders) isZero() bool {
return h.agreementPartyUInfo == nil &&
h.agreementPartyVInfo == nil &&
h.algorithm == nil &&
h.compression == nil &&
h.contentEncryption == nil &&
h.contentType == nil &&
h.critical == nil &&
h.ephemeralPublicKey == nil &&
h.jwk == nil &&
h.jwkSetURL == nil &&
h.keyID == nil &&
h.typ == nil &&
h.x509CertChain == nil &&
h.x509CertThumbprint == nil &&
h.x509CertThumbprintS256 == nil &&
h.x509URL == nil &&
len(h.privateParams) == 0
}
func (h *stdHeaders) Clone() (Headers, error) {
dst := NewHeaders()
if err := h.Copy(dst); err != nil {
return nil, fmt.Errorf(`failed to copy header contents to new object: %w`, err)
}
return dst, nil
}
func (h *stdHeaders) Copy(dst Headers) error {
for _, key := range h.Keys() {
var v any
if err := h.Get(key, &v); err != nil {
return fmt.Errorf(`jwe.Headers: Copy: failed to get header %q: %w`, key, err)
}
if err := dst.Set(key, v); err != nil {
return fmt.Errorf(`jwe.Headers: Copy: failed to set header %q: %w`, key, err)
}
}
return nil
}
func (h *stdHeaders) Merge(h2 Headers) (Headers, error) {
h3 := NewHeaders()
if h != nil {
if err := h.Copy(h3); err != nil {
return nil, fmt.Errorf(`failed to copy headers from receiver: %w`, err)
}
}
if h2 != nil {
if err := h2.Copy(h3); err != nil {
return nil, fmt.Errorf(`failed to copy headers from argument: %w`, err)
}
}
return h3, nil
}
func (h *stdHeaders) Encode() ([]byte, error) {
buf, err := json.Marshal(h)
if err != nil {
return nil, fmt.Errorf(`failed to marshal headers to JSON prior to encoding: %w`, err)
}
return base64.Encode(buf), nil
}
func (h *stdHeaders) Decode(buf []byte) error {
// base64 json string -> json object representation of header
decoded, err := base64.Decode(buf)
if err != nil {
return fmt.Errorf(`failed to unmarshal base64 encoded buffer: %w`, err)
}
if err := json.Unmarshal(decoded, h); err != nil {
return fmt.Errorf(`failed to unmarshal buffer: %w`, err)
}
return nil
}
+899
View File
@@ -0,0 +1,899 @@
// Code generated by tools/cmd/genjwe/main.go. DO NOT EDIT.
package jwe
import (
"bytes"
"fmt"
"sort"
"sync"
"github.com/lestrrat-go/blackmagic"
"github.com/lestrrat-go/jwx/v3/cert"
"github.com/lestrrat-go/jwx/v3/internal/base64"
"github.com/lestrrat-go/jwx/v3/internal/json"
"github.com/lestrrat-go/jwx/v3/internal/pool"
"github.com/lestrrat-go/jwx/v3/internal/tokens"
"github.com/lestrrat-go/jwx/v3/jwa"
"github.com/lestrrat-go/jwx/v3/jwk"
)
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"
)
// Headers describe a standard JWE Header set. It is part of the JWE message
// and is used to represent both Protected and Unprotected headers,
// which in turn can be found in each Recipient object.
// If you are not sure how this works, it is strongly recommended that
// you read RFC7516, especially the section
// that describes the full JSON serialization format of JWE messages.
//
// In most cases, you likely want to use the protected headers, as this is the part of the encrypted content
type Headers interface {
AgreementPartyUInfo() ([]byte, bool)
AgreementPartyVInfo() ([]byte, bool)
Algorithm() (jwa.KeyEncryptionAlgorithm, bool)
Compression() (jwa.CompressionAlgorithm, bool)
ContentEncryption() (jwa.ContentEncryptionAlgorithm, bool)
ContentType() (string, bool)
Critical() ([]string, bool)
EphemeralPublicKey() (jwk.Key, 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)
// 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, any) error
Set(string, any) 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
Encode() ([]byte, error)
Decode([]byte) error
Clone() (Headers, error)
Copy(Headers) error
Merge(Headers) (Headers, error)
// Keys returns a list of the keys contained in this header.
Keys() []string
}
// stdHeaderNames is a list of all standard header names defined in the JWE specification.
var stdHeaderNames = []string{AgreementPartyUInfoKey, AgreementPartyVInfoKey, AlgorithmKey, CompressionKey, ContentEncryptionKey, ContentTypeKey, CriticalKey, EphemeralPublicKeyKey, JWKKey, JWKSetURLKey, KeyIDKey, TypeKey, X509CertChainKey, X509CertThumbprintKey, X509CertThumbprintS256Key, X509URLKey}
type stdHeaders struct {
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
typ *string
x509CertChain *cert.Chain
x509CertThumbprint *string
x509CertThumbprintS256 *string
x509URL *string
privateParams map[string]any
mu *sync.RWMutex
}
func NewHeaders() Headers {
return &stdHeaders{
mu: &sync.RWMutex{},
privateParams: map[string]any{},
}
}
func (h *stdHeaders) AgreementPartyUInfo() ([]byte, bool) {
h.mu.RLock()
defer h.mu.RUnlock()
return h.agreementPartyUInfo, h.agreementPartyUInfo != nil
}
func (h *stdHeaders) AgreementPartyVInfo() ([]byte, bool) {
h.mu.RLock()
defer h.mu.RUnlock()
return h.agreementPartyVInfo, h.agreementPartyVInfo != nil
}
func (h *stdHeaders) Algorithm() (jwa.KeyEncryptionAlgorithm, bool) {
h.mu.RLock()
defer h.mu.RUnlock()
if h.algorithm == nil {
return jwa.EmptyKeyEncryptionAlgorithm(), false
}
return *(h.algorithm), true
}
func (h *stdHeaders) Compression() (jwa.CompressionAlgorithm, bool) {
h.mu.RLock()
defer h.mu.RUnlock()
if h.compression == nil {
return jwa.NoCompress(), false
}
return *(h.compression), true
}
func (h *stdHeaders) ContentEncryption() (jwa.ContentEncryptionAlgorithm, bool) {
h.mu.RLock()
defer h.mu.RUnlock()
if h.contentEncryption == nil {
return jwa.EmptyContentEncryptionAlgorithm(), false
}
return *(h.contentEncryption), true
}
func (h *stdHeaders) ContentType() (string, bool) {
h.mu.RLock()
defer h.mu.RUnlock()
if h.contentType == nil {
return "", false
}
return *(h.contentType), true
}
func (h *stdHeaders) Critical() ([]string, bool) {
h.mu.RLock()
defer h.mu.RUnlock()
return h.critical, h.critical != nil
}
func (h *stdHeaders) EphemeralPublicKey() (jwk.Key, bool) {
h.mu.RLock()
defer h.mu.RUnlock()
return h.ephemeralPublicKey, h.ephemeralPublicKey != nil
}
func (h *stdHeaders) JWK() (jwk.Key, bool) {
h.mu.RLock()
defer h.mu.RUnlock()
return h.jwk, h.jwk != nil
}
func (h *stdHeaders) JWKSetURL() (string, bool) {
h.mu.RLock()
defer h.mu.RUnlock()
if h.jwkSetURL == nil {
return "", false
}
return *(h.jwkSetURL), true
}
func (h *stdHeaders) KeyID() (string, bool) {
h.mu.RLock()
defer h.mu.RUnlock()
if h.keyID == nil {
return "", false
}
return *(h.keyID), true
}
func (h *stdHeaders) Type() (string, bool) {
h.mu.RLock()
defer h.mu.RUnlock()
if h.typ == nil {
return "", false
}
return *(h.typ), true
}
func (h *stdHeaders) X509CertChain() (*cert.Chain, bool) {
h.mu.RLock()
defer h.mu.RUnlock()
return h.x509CertChain, h.x509CertChain != nil
}
func (h *stdHeaders) X509CertThumbprint() (string, bool) {
h.mu.RLock()
defer h.mu.RUnlock()
if h.x509CertThumbprint == nil {
return "", false
}
return *(h.x509CertThumbprint), true
}
func (h *stdHeaders) X509CertThumbprintS256() (string, bool) {
h.mu.RLock()
defer h.mu.RUnlock()
if h.x509CertThumbprintS256 == nil {
return "", false
}
return *(h.x509CertThumbprintS256), true
}
func (h *stdHeaders) X509URL() (string, bool) {
h.mu.RLock()
defer h.mu.RUnlock()
if h.x509URL == nil {
return "", false
}
return *(h.x509URL), true
}
func (h *stdHeaders) PrivateParams() map[string]any {
h.mu.RLock()
defer h.mu.RUnlock()
return h.privateParams
}
func (h *stdHeaders) Has(name string) bool {
h.mu.RLock()
defer h.mu.RUnlock()
switch name {
case AgreementPartyUInfoKey:
return h.agreementPartyUInfo != nil
case AgreementPartyVInfoKey:
return h.agreementPartyVInfo != nil
case AlgorithmKey:
return h.algorithm != nil
case CompressionKey:
return h.compression != nil
case ContentEncryptionKey:
return h.contentEncryption != nil
case ContentTypeKey:
return h.contentType != nil
case CriticalKey:
return h.critical != nil
case EphemeralPublicKeyKey:
return h.ephemeralPublicKey != nil
case JWKKey:
return h.jwk != nil
case JWKSetURLKey:
return h.jwkSetURL != nil
case KeyIDKey:
return h.keyID != nil
case TypeKey:
return h.typ != nil
case X509CertChainKey:
return h.x509CertChain != nil
case X509CertThumbprintKey:
return h.x509CertThumbprint != nil
case X509CertThumbprintS256Key:
return h.x509CertThumbprintS256 != nil
case X509URLKey:
return h.x509URL != nil
default:
_, ok := h.privateParams[name]
return ok
}
}
func (h *stdHeaders) Get(name string, dst any) error {
h.mu.RLock()
defer h.mu.RUnlock()
switch name {
case AgreementPartyUInfoKey:
if h.agreementPartyUInfo == nil {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, h.agreementPartyUInfo); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
case AgreementPartyVInfoKey:
if h.agreementPartyVInfo == nil {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, h.agreementPartyVInfo); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
case AlgorithmKey:
if h.algorithm == nil {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, *(h.algorithm)); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
case CompressionKey:
if h.compression == nil {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, *(h.compression)); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
case ContentEncryptionKey:
if h.contentEncryption == nil {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, *(h.contentEncryption)); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
case ContentTypeKey:
if h.contentType == nil {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, *(h.contentType)); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
case CriticalKey:
if h.critical == nil {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, h.critical); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
case EphemeralPublicKeyKey:
if h.ephemeralPublicKey == nil {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, h.ephemeralPublicKey); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
case JWKKey:
if h.jwk == nil {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, h.jwk); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
case JWKSetURLKey:
if h.jwkSetURL == nil {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, *(h.jwkSetURL)); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
case KeyIDKey:
if h.keyID == nil {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, *(h.keyID)); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
case TypeKey:
if h.typ == nil {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, *(h.typ)); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
case X509CertChainKey:
if h.x509CertChain == nil {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, h.x509CertChain); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
case X509CertThumbprintKey:
if h.x509CertThumbprint == nil {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, *(h.x509CertThumbprint)); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
case X509CertThumbprintS256Key:
if h.x509CertThumbprintS256 == nil {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, *(h.x509CertThumbprintS256)); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
case X509URLKey:
if h.x509URL == nil {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, *(h.x509URL)); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
default:
v, ok := h.privateParams[name]
if !ok {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, v); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
}
return nil
}
func (h *stdHeaders) Set(name string, value any) error {
h.mu.Lock()
defer h.mu.Unlock()
return h.setNoLock(name, value)
}
func (h *stdHeaders) setNoLock(name string, value any) error {
switch name {
case AgreementPartyUInfoKey:
if v, ok := value.([]byte); ok {
h.agreementPartyUInfo = v
return nil
}
return fmt.Errorf(`invalid value for %s key: %T`, AgreementPartyUInfoKey, value)
case AgreementPartyVInfoKey:
if v, ok := value.([]byte); ok {
h.agreementPartyVInfo = v
return nil
}
return fmt.Errorf(`invalid value for %s key: %T`, AgreementPartyVInfoKey, value)
case AlgorithmKey:
if v, ok := value.(jwa.KeyEncryptionAlgorithm); ok {
h.algorithm = &v
return nil
}
return fmt.Errorf(`invalid value for %s key: %T`, AlgorithmKey, value)
case CompressionKey:
if v, ok := value.(jwa.CompressionAlgorithm); ok {
h.compression = &v
return nil
}
return fmt.Errorf(`invalid value for %s key: %T`, CompressionKey, value)
case ContentEncryptionKey:
if v, ok := value.(jwa.ContentEncryptionAlgorithm); ok {
if v == jwa.EmptyContentEncryptionAlgorithm() {
return fmt.Errorf(`"enc" field cannot be an empty string`)
}
h.contentEncryption = &v
return nil
}
return fmt.Errorf(`invalid value for %s key: %T`, ContentEncryptionKey, value)
case ContentTypeKey:
if v, ok := value.(string); ok {
h.contentType = &v
return nil
}
return fmt.Errorf(`invalid value for %s key: %T`, ContentTypeKey, value)
case CriticalKey:
if v, ok := value.([]string); ok {
h.critical = v
return nil
}
return fmt.Errorf(`invalid value for %s key: %T`, CriticalKey, value)
case EphemeralPublicKeyKey:
if v, ok := value.(jwk.Key); ok {
h.ephemeralPublicKey = v
return nil
}
return fmt.Errorf(`invalid value for %s key: %T`, EphemeralPublicKeyKey, value)
case JWKKey:
if v, ok := value.(jwk.Key); ok {
h.jwk = v
return nil
}
return fmt.Errorf(`invalid value for %s key: %T`, JWKKey, value)
case JWKSetURLKey:
if v, ok := value.(string); ok {
h.jwkSetURL = &v
return nil
}
return fmt.Errorf(`invalid value for %s key: %T`, JWKSetURLKey, value)
case KeyIDKey:
if v, ok := value.(string); ok {
h.keyID = &v
return nil
}
return fmt.Errorf(`invalid value for %s key: %T`, KeyIDKey, value)
case TypeKey:
if v, ok := value.(string); ok {
h.typ = &v
return nil
}
return fmt.Errorf(`invalid value for %s key: %T`, TypeKey, value)
case X509CertChainKey:
if v, ok := value.(*cert.Chain); ok {
h.x509CertChain = v
return nil
}
return fmt.Errorf(`invalid value for %s key: %T`, X509CertChainKey, value)
case X509CertThumbprintKey:
if v, ok := value.(string); ok {
h.x509CertThumbprint = &v
return nil
}
return fmt.Errorf(`invalid value for %s key: %T`, X509CertThumbprintKey, value)
case X509CertThumbprintS256Key:
if v, ok := value.(string); ok {
h.x509CertThumbprintS256 = &v
return nil
}
return fmt.Errorf(`invalid value for %s key: %T`, X509CertThumbprintS256Key, value)
case X509URLKey:
if v, ok := value.(string); ok {
h.x509URL = &v
return nil
}
return fmt.Errorf(`invalid value for %s key: %T`, X509URLKey, value)
default:
if h.privateParams == nil {
h.privateParams = map[string]any{}
}
h.privateParams[name] = value
}
return nil
}
func (h *stdHeaders) Remove(key string) error {
h.mu.Lock()
defer h.mu.Unlock()
switch key {
case AgreementPartyUInfoKey:
h.agreementPartyUInfo = nil
case AgreementPartyVInfoKey:
h.agreementPartyVInfo = nil
case AlgorithmKey:
h.algorithm = nil
case CompressionKey:
h.compression = nil
case ContentEncryptionKey:
h.contentEncryption = nil
case ContentTypeKey:
h.contentType = nil
case CriticalKey:
h.critical = nil
case EphemeralPublicKeyKey:
h.ephemeralPublicKey = nil
case JWKKey:
h.jwk = nil
case JWKSetURLKey:
h.jwkSetURL = nil
case KeyIDKey:
h.keyID = nil
case TypeKey:
h.typ = nil
case X509CertChainKey:
h.x509CertChain = nil
case X509CertThumbprintKey:
h.x509CertThumbprint = nil
case X509CertThumbprintS256Key:
h.x509CertThumbprintS256 = nil
case X509URLKey:
h.x509URL = nil
default:
delete(h.privateParams, key)
}
return nil
}
func (h *stdHeaders) UnmarshalJSON(buf []byte) error {
h.agreementPartyUInfo = nil
h.agreementPartyVInfo = nil
h.algorithm = nil
h.compression = nil
h.contentEncryption = nil
h.contentType = nil
h.critical = nil
h.ephemeralPublicKey = nil
h.jwk = nil
h.jwkSetURL = nil
h.keyID = nil
h.typ = nil
h.x509CertChain = nil
h.x509CertThumbprint = nil
h.x509CertThumbprintS256 = nil
h.x509URL = nil
dec := json.NewDecoder(bytes.NewReader(buf))
LOOP:
for {
tok, err := dec.Token()
if err != nil {
return fmt.Errorf(`error reading token: %w`, err)
}
switch tok := tok.(type) {
case json.Delim:
// Assuming we're doing everything correctly, we should ONLY
// get either tokens.OpenCurlyBracket or tokens.CloseCurlyBracket here.
if tok == tokens.CloseCurlyBracket { // End of object
break LOOP
} else if tok != tokens.OpenCurlyBracket {
return fmt.Errorf(`expected '%c' but got '%c'`, tokens.OpenCurlyBracket, tok)
}
case string: // Objects can only have string keys
switch tok {
case AgreementPartyUInfoKey:
if err := json.AssignNextBytesToken(&h.agreementPartyUInfo, dec); err != nil {
return fmt.Errorf(`failed to decode value for key %s: %w`, AgreementPartyUInfoKey, err)
}
case AgreementPartyVInfoKey:
if err := json.AssignNextBytesToken(&h.agreementPartyVInfo, dec); err != nil {
return fmt.Errorf(`failed to decode value for key %s: %w`, AgreementPartyVInfoKey, err)
}
case AlgorithmKey:
var decoded jwa.KeyEncryptionAlgorithm
if err := dec.Decode(&decoded); err != nil {
return fmt.Errorf(`failed to decode value for key %s: %w`, AlgorithmKey, err)
}
h.algorithm = &decoded
case CompressionKey:
var decoded jwa.CompressionAlgorithm
if err := dec.Decode(&decoded); err != nil {
return fmt.Errorf(`failed to decode value for key %s: %w`, CompressionKey, err)
}
h.compression = &decoded
case ContentEncryptionKey:
var decoded jwa.ContentEncryptionAlgorithm
if err := dec.Decode(&decoded); err != nil {
return fmt.Errorf(`failed to decode value for key %s: %w`, ContentEncryptionKey, err)
}
h.contentEncryption = &decoded
case ContentTypeKey:
if err := json.AssignNextStringToken(&h.contentType, dec); err != nil {
return fmt.Errorf(`failed to decode value for key %s: %w`, ContentTypeKey, err)
}
case CriticalKey:
var decoded []string
if err := dec.Decode(&decoded); err != nil {
return fmt.Errorf(`failed to decode value for key %s: %w`, CriticalKey, err)
}
h.critical = decoded
case EphemeralPublicKeyKey:
var buf json.RawMessage
if err := dec.Decode(&buf); err != nil {
return fmt.Errorf(`failed to decode value for key %s:%w`, EphemeralPublicKeyKey, err)
}
key, err := jwk.ParseKey(buf)
if err != nil {
return fmt.Errorf(`failed to parse JWK for key %s: %w`, EphemeralPublicKeyKey, err)
}
h.ephemeralPublicKey = key
case JWKKey:
var buf json.RawMessage
if err := dec.Decode(&buf); err != nil {
return fmt.Errorf(`failed to decode value for key %s:%w`, JWKKey, err)
}
key, err := jwk.ParseKey(buf)
if err != nil {
return fmt.Errorf(`failed to parse JWK for key %s: %w`, JWKKey, err)
}
h.jwk = key
case JWKSetURLKey:
if err := json.AssignNextStringToken(&h.jwkSetURL, dec); err != nil {
return fmt.Errorf(`failed to decode value for key %s: %w`, JWKSetURLKey, err)
}
case KeyIDKey:
if err := json.AssignNextStringToken(&h.keyID, dec); err != nil {
return fmt.Errorf(`failed to decode value for key %s: %w`, KeyIDKey, err)
}
case TypeKey:
if err := json.AssignNextStringToken(&h.typ, dec); err != nil {
return fmt.Errorf(`failed to decode value for key %s: %w`, TypeKey, err)
}
case X509CertChainKey:
var decoded cert.Chain
if err := dec.Decode(&decoded); err != nil {
return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertChainKey, err)
}
h.x509CertChain = &decoded
case X509CertThumbprintKey:
if err := json.AssignNextStringToken(&h.x509CertThumbprint, dec); err != nil {
return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintKey, err)
}
case X509CertThumbprintS256Key:
if err := json.AssignNextStringToken(&h.x509CertThumbprintS256, dec); err != nil {
return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintS256Key, err)
}
case X509URLKey:
if err := json.AssignNextStringToken(&h.x509URL, dec); err != nil {
return fmt.Errorf(`failed to decode value for key %s: %w`, X509URLKey, err)
}
default:
decoded, err := registry.Decode(dec, tok)
if err != nil {
return err
}
h.setNoLock(tok, decoded)
}
default:
return fmt.Errorf(`invalid token %T`, tok)
}
}
return nil
}
func (h *stdHeaders) Keys() []string {
h.mu.RLock()
defer h.mu.RUnlock()
keys := make([]string, 0, 16+len(h.privateParams))
if h.agreementPartyUInfo != nil {
keys = append(keys, AgreementPartyUInfoKey)
}
if h.agreementPartyVInfo != nil {
keys = append(keys, AgreementPartyVInfoKey)
}
if h.algorithm != nil {
keys = append(keys, AlgorithmKey)
}
if h.compression != nil {
keys = append(keys, CompressionKey)
}
if h.contentEncryption != nil {
keys = append(keys, ContentEncryptionKey)
}
if h.contentType != nil {
keys = append(keys, ContentTypeKey)
}
if h.critical != nil {
keys = append(keys, CriticalKey)
}
if h.ephemeralPublicKey != nil {
keys = append(keys, EphemeralPublicKeyKey)
}
if h.jwk != nil {
keys = append(keys, JWKKey)
}
if h.jwkSetURL != nil {
keys = append(keys, JWKSetURLKey)
}
if h.keyID != nil {
keys = append(keys, KeyIDKey)
}
if h.typ != nil {
keys = append(keys, TypeKey)
}
if h.x509CertChain != nil {
keys = append(keys, X509CertChainKey)
}
if h.x509CertThumbprint != nil {
keys = append(keys, X509CertThumbprintKey)
}
if h.x509CertThumbprintS256 != nil {
keys = append(keys, X509CertThumbprintS256Key)
}
if h.x509URL != nil {
keys = append(keys, X509URLKey)
}
for k := range h.privateParams {
keys = append(keys, k)
}
return keys
}
func (h stdHeaders) MarshalJSON() ([]byte, error) {
data := make(map[string]any)
keys := make([]string, 0, 16+len(h.privateParams))
h.mu.RLock()
if h.agreementPartyUInfo != nil {
data[AgreementPartyUInfoKey] = h.agreementPartyUInfo
keys = append(keys, AgreementPartyUInfoKey)
}
if h.agreementPartyVInfo != nil {
data[AgreementPartyVInfoKey] = h.agreementPartyVInfo
keys = append(keys, AgreementPartyVInfoKey)
}
if h.algorithm != nil {
data[AlgorithmKey] = *(h.algorithm)
keys = append(keys, AlgorithmKey)
}
if h.compression != nil {
data[CompressionKey] = *(h.compression)
keys = append(keys, CompressionKey)
}
if h.contentEncryption != nil {
data[ContentEncryptionKey] = *(h.contentEncryption)
keys = append(keys, ContentEncryptionKey)
}
if h.contentType != nil {
data[ContentTypeKey] = *(h.contentType)
keys = append(keys, ContentTypeKey)
}
if h.critical != nil {
data[CriticalKey] = h.critical
keys = append(keys, CriticalKey)
}
if h.ephemeralPublicKey != nil {
data[EphemeralPublicKeyKey] = h.ephemeralPublicKey
keys = append(keys, EphemeralPublicKeyKey)
}
if h.jwk != nil {
data[JWKKey] = h.jwk
keys = append(keys, JWKKey)
}
if h.jwkSetURL != nil {
data[JWKSetURLKey] = *(h.jwkSetURL)
keys = append(keys, JWKSetURLKey)
}
if h.keyID != nil {
data[KeyIDKey] = *(h.keyID)
keys = append(keys, KeyIDKey)
}
if h.typ != nil {
data[TypeKey] = *(h.typ)
keys = append(keys, TypeKey)
}
if h.x509CertChain != nil {
data[X509CertChainKey] = h.x509CertChain
keys = append(keys, X509CertChainKey)
}
if h.x509CertThumbprint != nil {
data[X509CertThumbprintKey] = *(h.x509CertThumbprint)
keys = append(keys, X509CertThumbprintKey)
}
if h.x509CertThumbprintS256 != nil {
data[X509CertThumbprintS256Key] = *(h.x509CertThumbprintS256)
keys = append(keys, X509CertThumbprintS256Key)
}
if h.x509URL != nil {
data[X509URLKey] = *(h.x509URL)
keys = append(keys, X509URLKey)
}
for k, v := range h.privateParams {
data[k] = v
keys = append(keys, k)
}
h.mu.RUnlock()
sort.Strings(keys)
buf := pool.BytesBuffer().Get()
defer pool.BytesBuffer().Put(buf)
enc := json.NewEncoder(buf)
buf.WriteByte(tokens.OpenCurlyBracket)
for i, k := range keys {
if i > 0 {
buf.WriteRune(tokens.Comma)
}
buf.WriteRune(tokens.DoubleQuote)
buf.WriteString(k)
buf.WriteString(`":`)
v := data[k]
switch v := v.(type) {
case []byte:
buf.WriteRune(tokens.DoubleQuote)
buf.WriteString(base64.EncodeToString(v))
buf.WriteRune(tokens.DoubleQuote)
default:
if err := enc.Encode(v); err != nil {
return nil, fmt.Errorf(`failed to encode value for field %s`, k)
}
buf.Truncate(buf.Len() - 1)
}
}
buf.WriteByte(tokens.CloseCurlyBracket)
ret := make([]byte, buf.Len())
copy(ret, buf.Bytes())
return ret, nil
}
func (h *stdHeaders) clear() {
h.mu.Lock()
h.agreementPartyUInfo = nil
h.agreementPartyVInfo = nil
h.algorithm = nil
h.compression = nil
h.contentEncryption = nil
h.contentType = nil
h.critical = nil
h.ephemeralPublicKey = nil
h.jwk = nil
h.jwkSetURL = nil
h.keyID = nil
h.typ = nil
h.x509CertChain = nil
h.x509CertThumbprint = nil
h.x509CertThumbprintS256 = nil
h.x509URL = nil
h.privateParams = map[string]any{}
h.mu.Unlock()
}
+207
View File
@@ -0,0 +1,207 @@
package jwe
import (
"github.com/lestrrat-go/jwx/v3/jwa"
"github.com/lestrrat-go/jwx/v3/jwe/internal/keygen"
)
// KeyEncrypter is an interface for object that can encrypt a
// content encryption key.
//
// You can use this in place of a regular key (i.e. in jwe.WithKey())
// to encrypt the content encryption key in a JWE message without
// having to expose the secret key in memory, for example, when you
// want to use hardware security modules (HSMs) to encrypt the key.
//
// This API is experimental and may change without notice, even
// in minor releases.
type KeyEncrypter interface {
// Algorithm returns the algorithm used to encrypt the key.
Algorithm() jwa.KeyEncryptionAlgorithm
// EncryptKey encrypts the given content encryption key.
EncryptKey([]byte) ([]byte, error)
}
// KeyIDer is an interface for things that can return a key ID.
//
// As of this writing, this is solely used to identify KeyEncrypter
// objects that also carry a key ID on its own.
type KeyIDer interface {
KeyID() (string, bool)
}
// KeyDecrypter is an interface for objects that can decrypt a content
// encryption key.
//
// You can use this in place of a regular key (i.e. in jwe.WithKey())
// to decrypt the encrypted key in a JWE message without having to
// expose the secret key in memory, for example, when you want to use
// hardware security modules (HSMs) to decrypt the key.
//
// This API is experimental and may change without notice, even
// in minor releases.
type KeyDecrypter interface {
// Decrypt decrypts the encrypted key of a JWE message.
//
// Make sure you understand how JWE messages are structured.
//
// For example, while in most circumstances a JWE message will only have one recipient,
// a JWE message may contain multiple recipients, each with their own
// encrypted key. This method will be called for each recipient, instead of
// just once for a message.
//
// Also, header values could be found in either protected/unprotected headers
// of a JWE message, as well as in protected/unprotected headers for each recipient.
// When checking a header value, you can decide to use either one, or both, but you
// must be aware that there are multiple places to look for.
DecryptKey(alg jwa.KeyEncryptionAlgorithm, encryptedKey []byte, recipient Recipient, message *Message) ([]byte, error)
}
// Recipient holds the encrypted key and hints to decrypt the key
type Recipient interface {
Headers() Headers
EncryptedKey() []byte
SetHeaders(Headers) error
SetEncryptedKey([]byte) error
}
type stdRecipient struct {
// Comments on each field are taken from https://datatracker.ietf.org/doc/html/rfc7516
//
// header
// The "header" member MUST be present and contain the value JWE Per-
// Recipient Unprotected Header when the JWE Per-Recipient
// Unprotected Header value is non-empty; otherwise, it MUST be
// absent. This value is represented as an unencoded JSON object,
// rather than as a string. These Header Parameter values are not
// integrity protected.
//
// At least one of the "header", "protected", and "unprotected" members
// MUST be present so that "alg" and "enc" Header Parameter values are
// conveyed for each recipient computation.
//
// JWX note: see Message.unprotectedHeaders
headers Headers
// encrypted_key
// The "encrypted_key" member MUST be present and contain the value
// BASE64URL(JWE Encrypted Key) when the JWE Encrypted Key value is
// non-empty; otherwise, it MUST be absent.
encryptedKey []byte
}
// 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.
//
//nolint:govet
type Message struct {
// Comments on each field are taken from https://datatracker.ietf.org/doc/html/rfc7516
//
// protected
// The "protected" member MUST be present and contain the value
// BASE64URL(UTF8(JWE Protected Header)) when the JWE Protected
// Header value is non-empty; otherwise, it MUST be absent. These
// Header Parameter values are integrity protected.
protectedHeaders Headers
// unprotected
// The "unprotected" member MUST be present and contain the value JWE
// Shared Unprotected Header when the JWE Shared Unprotected Header
// value is non-empty; otherwise, it MUST be absent. This value is
// represented as an unencoded JSON object, rather than as a string.
// These Header Parameter values are not integrity protected.
//
// JWX note: This field is NOT mutually exclusive with per-recipient
// headers within the implementation because... it's too much work.
// It is _never_ populated (we don't provide a way to do this) upon encryption.
// When decrypting, if present its values are always merged with
// per-recipient header.
unprotectedHeaders Headers
// iv
// The "iv" member MUST be present and contain the value
// BASE64URL(JWE Initialization Vector) when the JWE Initialization
// Vector value is non-empty; otherwise, it MUST be absent.
initializationVector []byte
// aad
// The "aad" member MUST be present and contain the value
// BASE64URL(JWE AAD)) when the JWE AAD value is non-empty;
// otherwise, it MUST be absent. A JWE AAD value can be included to
// supply a base64url-encoded value to be integrity protected but not
// encrypted.
authenticatedData []byte
// ciphertext
// The "ciphertext" member MUST be present and contain the value
// BASE64URL(JWE Ciphertext).
cipherText []byte
// tag
// The "tag" member MUST be present and contain the value
// BASE64URL(JWE Authentication Tag) when the JWE Authentication Tag
// value is non-empty; otherwise, it MUST be absent.
tag []byte
// recipients
// The "recipients" member value MUST be an array of JSON objects.
// Each object contains information specific to a single recipient.
// This member MUST be present with exactly one array element per
// recipient, even if some or all of the array element values are the
// empty JSON object "{}" (which can happen when all Header Parameter
// values are shared between all recipients and when no encrypted key
// is used, such as when doing Direct Encryption).
//
// Some Header Parameters, including the "alg" parameter, can be shared
// among all recipient computations. Header Parameters in the JWE
// Protected Header and JWE Shared Unprotected Header values are shared
// among all recipients.
//
// The Header Parameter values used when creating or validating per-
// recipient ciphertext and Authentication Tag values are the union of
// the three sets of Header Parameter values that may be present: (1)
// the JWE Protected Header represented in the "protected" member, (2)
// the JWE Shared Unprotected Header represented in the "unprotected"
// member, and (3) the JWE Per-Recipient Unprotected Header represented
// in the "header" member of the recipient's array element. The union
// of these sets of Header Parameters comprises the JOSE Header. The
// Header Parameter names in the three locations MUST be disjoint.
recipients []Recipient
// TODO: Additional members can be present in both the JSON objects defined
// above; if not understood by implementations encountering them, they
// MUST be ignored.
// privateParams map[string]any
// These two fields below are not available for the public consumers of this object.
// rawProtectedHeaders stores the original protected header buffer
rawProtectedHeaders []byte
// storeProtectedHeaders is a hint to be used in UnmarshalJSON().
// When this flag is true, UnmarshalJSON() will populate the
// rawProtectedHeaders field
storeProtectedHeaders bool
}
// populater is an interface for things that may modify the
// JWE header. e.g. ByteWithECPrivateKey
type populater interface {
Populate(keygen.Setter) error
}
@@ -0,0 +1,22 @@
load("@rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "aescbc",
srcs = ["aescbc.go"],
importpath = "github.com/lestrrat-go/jwx/v3/jwe/internal/aescbc",
visibility = ["//:__subpackages__"],
deps = ["//internal/pool"],
)
go_test(
name = "aescbc_test",
srcs = ["aescbc_test.go"],
embed = [":aescbc"],
deps = ["@com_github_stretchr_testify//require"]
)
alias(
name = "go_default_library",
actual = ":aescbc",
visibility = ["//jwe:__subpackages__"],
)
@@ -0,0 +1,268 @@
package aescbc
import (
"crypto/cipher"
"crypto/hmac"
"crypto/sha256"
"crypto/sha512"
"crypto/subtle"
"encoding/binary"
"errors"
"fmt"
"hash"
"sync/atomic"
"github.com/lestrrat-go/jwx/v3/internal/pool"
)
const (
NonceSize = 16
)
const defaultBufSize int64 = 256 * 1024 * 1024
var maxBufSize atomic.Int64
func init() {
SetMaxBufferSize(defaultBufSize)
}
func SetMaxBufferSize(siz int64) {
if siz <= 0 {
siz = defaultBufSize
}
maxBufSize.Store(siz)
}
func pad(buf []byte, n int) []byte {
rem := n - len(buf)%n
if rem == 0 {
return buf
}
bufsiz := len(buf) + rem
mbs := maxBufSize.Load()
if int64(bufsiz) > mbs {
panic(fmt.Errorf("failed to allocate buffer"))
}
newbuf := make([]byte, bufsiz)
copy(newbuf, buf)
for i := len(buf); i < len(newbuf); i++ {
newbuf[i] = byte(rem)
}
return newbuf
}
// ref. https://github.com/golang/go/blob/c3db64c0f45e8f2d75c5b59401e0fc925701b6f4/src/crypto/tls/conn.go#L279-L324
//
// extractPadding returns, in constant time, the length of the padding to remove
// from the end of payload. It also returns a byte which is equal to 255 if the
// padding was valid and 0 otherwise. See RFC 2246, Section 6.2.3.2.
func extractPadding(payload []byte) (toRemove int, good byte) {
if len(payload) < 1 {
return 0, 0
}
paddingLen := payload[len(payload)-1]
t := uint(len(payload)) - uint(paddingLen)
// if len(payload) > paddingLen then the MSB of t is zero
good = byte(int32(^t) >> 31)
// The maximum possible padding length plus the actual length field
toCheck := 256
// The length of the padded data is public, so we can use an if here
toCheck = min(toCheck, len(payload))
for i := 1; i <= toCheck; i++ {
t := uint(paddingLen) - uint(i)
// if i <= paddingLen then the MSB of t is zero
mask := byte(int32(^t) >> 31)
b := payload[len(payload)-i]
good &^= mask&paddingLen ^ mask&b
}
// We AND together the bits of good and replicate the result across
// all the bits.
good &= good << 4
good &= good << 2
good &= good << 1
good = uint8(int8(good) >> 7)
// Zero the padding length on error. This ensures any unchecked bytes
// are included in the MAC. Otherwise, an attacker that could
// distinguish MAC failures from padding failures could mount an attack
// similar to POODLE in SSL 3.0: given a good ciphertext that uses a
// full block's worth of padding, replace the final block with another
// block. If the MAC check passed but the padding check failed, the
// last byte of that block decrypted to the block size.
//
// See also macAndPaddingGood logic below.
paddingLen &= good
toRemove = int(paddingLen)
return
}
type Hmac struct {
blockCipher cipher.Block
hash func() hash.Hash
keysize int
tagsize int
integrityKey []byte
}
type BlockCipherFunc func([]byte) (cipher.Block, error)
func New(key []byte, f BlockCipherFunc) (hmac *Hmac, err error) {
keysize := len(key) / 2
ikey := key[:keysize]
ekey := key[keysize:]
bc, ciphererr := f(ekey)
if ciphererr != nil {
err = fmt.Errorf(`failed to execute block cipher function: %w`, ciphererr)
return
}
var hfunc func() hash.Hash
switch keysize {
case 16:
hfunc = sha256.New
case 24:
hfunc = sha512.New384
case 32:
hfunc = sha512.New
default:
return nil, fmt.Errorf("unsupported key size %d", keysize)
}
return &Hmac{
blockCipher: bc,
hash: hfunc,
integrityKey: ikey,
keysize: keysize,
tagsize: keysize, // NonceSize,
// While investigating GH #207, I stumbled upon another problem where
// the computed tags don't match on decrypt. After poking through the
// code using a bunch of debug statements, I've finally found out that
// tagsize = keysize makes the whole thing work.
}, nil
}
// NonceSize fulfills the crypto.AEAD interface
func (c Hmac) NonceSize() int {
return NonceSize
}
// Overhead fulfills the crypto.AEAD interface
func (c Hmac) Overhead() int {
return c.blockCipher.BlockSize() + c.tagsize
}
func (c Hmac) ComputeAuthTag(aad, nonce, ciphertext []byte) ([]byte, error) {
var buf [8]byte
binary.BigEndian.PutUint64(buf[:], uint64(len(aad)*8))
h := hmac.New(c.hash, c.integrityKey)
// compute the tag
// no need to check errors because Write never returns an error: https://pkg.go.dev/hash#Hash
//
// > Write (via the embedded io.Writer interface) adds more data to the running hash.
// > It never returns an error.
h.Write(aad)
h.Write(nonce)
h.Write(ciphertext)
h.Write(buf[:])
s := h.Sum(nil)
return s[:c.tagsize], nil
}
func ensureSize(dst []byte, n int) []byte {
// if the dst buffer has enough length just copy the relevant parts to it.
// Otherwise create a new slice that's big enough, and operate on that
// Note: I think go-jose has a bug in that it checks for cap(), but not len().
ret := dst
if diff := n - len(dst); diff > 0 {
// dst is not big enough
ret = make([]byte, n)
copy(ret, dst)
}
return ret
}
// Seal fulfills the crypto.AEAD interface
func (c Hmac) Seal(dst, nonce, plaintext, data []byte) []byte {
ctlen := len(plaintext)
bufsiz := ctlen + c.Overhead()
mbs := maxBufSize.Load()
if int64(bufsiz) > mbs {
panic(fmt.Errorf("failed to allocate buffer"))
}
ciphertext := make([]byte, bufsiz)[:ctlen]
copy(ciphertext, plaintext)
ciphertext = pad(ciphertext, c.blockCipher.BlockSize())
cbc := cipher.NewCBCEncrypter(c.blockCipher, nonce)
cbc.CryptBlocks(ciphertext, ciphertext)
authtag, err := c.ComputeAuthTag(data, nonce, ciphertext)
if err != nil {
// Hmac implements cipher.AEAD interface. Seal can't return error.
// But currently it never reach here because of Hmac.ComputeAuthTag doesn't return error.
panic(fmt.Errorf("failed to seal on hmac: %v", err))
}
retlen := len(dst) + len(ciphertext) + len(authtag)
ret := ensureSize(dst, retlen)
out := ret[len(dst):]
n := copy(out, ciphertext)
copy(out[n:], authtag)
return ret
}
// Open fulfills the crypto.AEAD interface
func (c Hmac) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
if len(ciphertext) < c.keysize {
return nil, fmt.Errorf(`invalid ciphertext (too short)`)
}
tagOffset := len(ciphertext) - c.tagsize
if tagOffset%c.blockCipher.BlockSize() != 0 {
return nil, fmt.Errorf(
"invalid ciphertext (invalid length: %d %% %d != 0)",
tagOffset,
c.blockCipher.BlockSize(),
)
}
tag := ciphertext[tagOffset:]
ciphertext = ciphertext[:tagOffset]
expectedTag, err := c.ComputeAuthTag(data, nonce, ciphertext[:tagOffset])
if err != nil {
return nil, fmt.Errorf(`failed to compute auth tag: %w`, err)
}
cbc := cipher.NewCBCDecrypter(c.blockCipher, nonce)
buf := pool.ByteSlice().GetCapacity(tagOffset)
defer pool.ByteSlice().Put(buf)
buf = buf[:tagOffset]
cbc.CryptBlocks(buf, ciphertext)
toRemove, good := extractPadding(buf)
cmp := subtle.ConstantTimeCompare(expectedTag, tag) & int(good)
if cmp != 1 {
return nil, errors.New(`invalid ciphertext`)
}
plaintext := buf[:len(buf)-toRemove]
ret := ensureSize(dst, len(plaintext))
out := ret[len(dst):]
copy(out, plaintext)
return ret, nil
}
@@ -0,0 +1,34 @@
load("@rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "cipher",
srcs = [
"cipher.go",
"interface.go",
],
importpath = "github.com/lestrrat-go/jwx/v3/jwe/internal/cipher",
visibility = ["//:__subpackages__"],
deps = [
"//jwa",
"//jwe/internal/aescbc",
"//jwe/internal/keygen",
"//internal/tokens",
],
)
go_test(
name = "cipher_test",
srcs = ["cipher_test.go"],
deps = [
":cipher",
"//jwa",
"//internal/tokens",
"@com_github_stretchr_testify//require",
],
)
alias(
name = "go_default_library",
actual = ":cipher",
visibility = ["//jwe:__subpackages__"],
)
@@ -0,0 +1,169 @@
package cipher
import (
"crypto/aes"
"crypto/cipher"
"fmt"
"github.com/lestrrat-go/jwx/v3/internal/tokens"
"github.com/lestrrat-go/jwx/v3/jwe/internal/aescbc"
"github.com/lestrrat-go/jwx/v3/jwe/internal/keygen"
)
var gcm = &gcmFetcher{}
var cbc = &cbcFetcher{}
func (f gcmFetcher) Fetch(key []byte, size int) (cipher.AEAD, error) {
if len(key) != size {
return nil, fmt.Errorf(`key size (%d) does not match expected key size (%d)`, len(key), size)
}
aescipher, err := aes.NewCipher(key)
if err != nil {
return nil, fmt.Errorf(`cipher: failed to create AES cipher for GCM: %w`, err)
}
aead, err := cipher.NewGCM(aescipher)
if err != nil {
return nil, fmt.Errorf(`failed to create GCM for cipher: %w`, err)
}
return aead, nil
}
func (f cbcFetcher) Fetch(key []byte, size int) (cipher.AEAD, error) {
if len(key) != size {
return nil, fmt.Errorf(`key size (%d) does not match expected key size (%d)`, len(key), size)
}
aead, err := aescbc.New(key, aes.NewCipher)
if err != nil {
return nil, fmt.Errorf(`cipher: failed to create AES cipher for CBC: %w`, err)
}
return aead, nil
}
func (c AesContentCipher) KeySize() int {
return c.keysize
}
func (c AesContentCipher) TagSize() int {
return c.tagsize
}
func NewAES(alg string) (*AesContentCipher, error) {
var keysize int
var tagsize int
var fetcher Fetcher
switch alg {
case tokens.A128GCM:
keysize = 16
tagsize = 16
fetcher = gcm
case tokens.A192GCM:
keysize = 24
tagsize = 16
fetcher = gcm
case tokens.A256GCM:
keysize = 32
tagsize = 16
fetcher = gcm
case tokens.A128CBC_HS256:
tagsize = 16
keysize = tagsize * 2
fetcher = cbc
case tokens.A192CBC_HS384:
tagsize = 24
keysize = tagsize * 2
fetcher = cbc
case tokens.A256CBC_HS512:
tagsize = 32
keysize = tagsize * 2
fetcher = cbc
default:
return nil, fmt.Errorf("failed to create AES content cipher: invalid algorithm (%s)", alg)
}
return &AesContentCipher{
keysize: keysize,
tagsize: tagsize,
fetch: fetcher,
}, nil
}
func (c AesContentCipher) Encrypt(cek, plaintext, aad []byte) (iv, ciphertxt, tag []byte, err error) {
var aead cipher.AEAD
aead, err = c.fetch.Fetch(cek, c.keysize)
if err != nil {
return nil, nil, nil, fmt.Errorf(`failed to fetch AEAD: %w`, err)
}
// Seal may panic (argh!), so protect ourselves from that
defer func() {
if e := recover(); e != nil {
switch e := e.(type) {
case error:
err = e
default:
err = fmt.Errorf("%s", e)
}
err = fmt.Errorf(`failed to encrypt: %w`, err)
}
}()
if c.NonceGenerator != nil {
iv, err = c.NonceGenerator(aead.NonceSize())
if err != nil {
return nil, nil, nil, fmt.Errorf(`failed to generate nonce: %w`, err)
}
} else {
bs, err := keygen.Random(aead.NonceSize())
if err != nil {
return nil, nil, nil, fmt.Errorf(`failed to generate random nonce: %w`, err)
}
iv = bs.Bytes()
}
combined := aead.Seal(nil, iv, plaintext, aad)
tagoffset := len(combined) - c.TagSize()
if tagoffset < 0 {
panic(fmt.Sprintf("tag offset is less than 0 (combined len = %d, tagsize = %d)", len(combined), c.TagSize()))
}
tag = combined[tagoffset:]
ciphertxt = make([]byte, tagoffset)
copy(ciphertxt, combined[:tagoffset])
return
}
func (c AesContentCipher) Decrypt(cek, iv, ciphertxt, tag, aad []byte) (plaintext []byte, err error) {
aead, err := c.fetch.Fetch(cek, c.keysize)
if err != nil {
return nil, fmt.Errorf(`failed to fetch AEAD data: %w`, err)
}
// Open may panic (argh!), so protect ourselves from that
defer func() {
if e := recover(); e != nil {
switch e := e.(type) {
case error:
err = e
default:
err = fmt.Errorf(`%s`, e)
}
err = fmt.Errorf(`failed to decrypt: %w`, err)
return
}
}()
combined := make([]byte, len(ciphertxt)+len(tag))
copy(combined, ciphertxt)
copy(combined[len(ciphertxt):], tag)
buf, aeaderr := aead.Open(nil, iv, combined, aad)
if aeaderr != nil {
err = fmt.Errorf(`aead.Open failed: %w`, aeaderr)
return
}
plaintext = buf
return
}
@@ -0,0 +1,32 @@
package cipher
import (
"crypto/cipher"
)
const (
TagSize = 16
)
// ContentCipher knows how to encrypt/decrypt the content given a content
// encryption key and other data
type ContentCipher interface {
KeySize() int
Encrypt(cek, aad, plaintext []byte) ([]byte, []byte, []byte, error)
Decrypt(cek, iv, aad, ciphertext, tag []byte) ([]byte, error)
}
type Fetcher interface {
Fetch([]byte, int) (cipher.AEAD, error)
}
type gcmFetcher struct{}
type cbcFetcher struct{}
// AesContentCipher represents a cipher based on AES
type AesContentCipher struct {
NonceGenerator func(int) ([]byte, error)
fetch Fetcher
keysize int
tagsize int
}
@@ -0,0 +1,24 @@
load("@rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "concatkdf",
srcs = ["concatkdf.go"],
importpath = "github.com/lestrrat-go/jwx/v3/jwe/internal/concatkdf",
visibility = ["//:__subpackages__"],
)
go_test(
name = "concatkdf_test",
srcs = ["concatkdf_test.go"],
embed = [":concatkdf"],
deps = [
"//jwa",
"@com_github_stretchr_testify//require",
],
)
alias(
name = "go_default_library",
actual = ":concatkdf",
visibility = ["//jwe:__subpackages__"],
)
@@ -0,0 +1,66 @@
package concatkdf
import (
"crypto"
"encoding/binary"
"fmt"
)
type KDF struct {
buf []byte
otherinfo []byte
z []byte
hash crypto.Hash
}
func ndata(src []byte) []byte {
buf := make([]byte, 4+len(src))
binary.BigEndian.PutUint32(buf, uint32(len(src)))
copy(buf[4:], src)
return buf
}
func New(hash crypto.Hash, alg, Z, apu, apv, pubinfo, privinfo []byte) *KDF {
algbuf := ndata(alg)
apubuf := ndata(apu)
apvbuf := ndata(apv)
concat := make([]byte, len(algbuf)+len(apubuf)+len(apvbuf)+len(pubinfo)+len(privinfo))
n := copy(concat, algbuf)
n += copy(concat[n:], apubuf)
n += copy(concat[n:], apvbuf)
n += copy(concat[n:], pubinfo)
copy(concat[n:], privinfo)
return &KDF{
hash: hash,
otherinfo: concat,
z: Z,
}
}
func (k *KDF) Read(out []byte) (int, error) {
var round uint32 = 1
h := k.hash.New()
for len(out) > len(k.buf) {
h.Reset()
if err := binary.Write(h, binary.BigEndian, round); err != nil {
return 0, fmt.Errorf(`failed to write round using kdf: %w`, err)
}
if _, err := h.Write(k.z); err != nil {
return 0, fmt.Errorf(`failed to write z using kdf: %w`, err)
}
if _, err := h.Write(k.otherinfo); err != nil {
return 0, fmt.Errorf(`failed to write other info using kdf: %w`, err)
}
k.buf = append(k.buf, h.Sum(nil)...)
round++
}
n := copy(out, k.buf[:len(out)])
k.buf = k.buf[len(out):]
return n, nil
}
@@ -0,0 +1,21 @@
load("@rules_go//go:def.bzl", "go_library")
go_library(
name = "content_crypt",
srcs = [
"content_crypt.go",
"interface.go",
],
importpath = "github.com/lestrrat-go/jwx/v3/jwe/internal/content_crypt",
visibility = ["//:__subpackages__"],
deps = [
"//jwa",
"//jwe/internal/cipher",
],
)
alias(
name = "go_default_library",
actual = ":content_crypt",
visibility = ["//jwe:__subpackages__"],
)
@@ -0,0 +1,43 @@
package content_crypt //nolint:golint
import (
"fmt"
"github.com/lestrrat-go/jwx/v3/jwa"
"github.com/lestrrat-go/jwx/v3/jwe/internal/cipher"
)
func (c Generic) Algorithm() jwa.ContentEncryptionAlgorithm {
return c.alg
}
func (c Generic) Encrypt(cek, plaintext, aad []byte) ([]byte, []byte, []byte, error) {
iv, encrypted, tag, err := c.cipher.Encrypt(cek, plaintext, aad)
if err != nil {
return nil, nil, nil, fmt.Errorf(`failed to crypt content: %w`, err)
}
return iv, encrypted, tag, nil
}
func (c Generic) Decrypt(cek, iv, ciphertext, tag, aad []byte) ([]byte, error) {
return c.cipher.Decrypt(cek, iv, ciphertext, tag, aad)
}
func NewGeneric(alg jwa.ContentEncryptionAlgorithm) (*Generic, error) {
c, err := cipher.NewAES(alg.String())
if err != nil {
return nil, fmt.Errorf(`aes crypt: failed to create content cipher: %w`, err)
}
return &Generic{
alg: alg,
cipher: c,
keysize: c.KeySize(),
tagsize: 16,
}, nil
}
func (c Generic) KeySize() int {
return c.keysize
}
@@ -0,0 +1,20 @@
package content_crypt //nolint:golint
import (
"github.com/lestrrat-go/jwx/v3/jwa"
"github.com/lestrrat-go/jwx/v3/jwe/internal/cipher"
)
// Generic encrypts a message by applying all the necessary
// modifications to the keys and the contents
type Generic struct {
alg jwa.ContentEncryptionAlgorithm
keysize int
tagsize int
cipher cipher.ContentCipher
}
type Cipher interface {
Decrypt([]byte, []byte, []byte, []byte, []byte) ([]byte, error)
KeySize() int
}
@@ -0,0 +1,24 @@
load("@rules_go//go:def.bzl", "go_library")
go_library(
name = "keygen",
srcs = [
"interface.go",
"keygen.go",
],
importpath = "github.com/lestrrat-go/jwx/v3/jwe/internal/keygen",
visibility = ["//:__subpackages__"],
deps = [
"//internal/ecutil",
"//jwa",
"//jwe/internal/concatkdf",
"//internal/tokens",
"//jwk",
],
)
alias(
name = "go_default_library",
actual = ":keygen",
visibility = ["//jwe:__subpackages__"],
)
@@ -0,0 +1,40 @@
package keygen
// ByteKey is a generated key that only has the key's byte buffer
// as its instance data. If a key needs to do more, such as providing
// values to be set in a JWE header, that key type wraps a ByteKey
type ByteKey []byte
// ByteWithECPublicKey holds the EC private key that generated
// the key along with the key itself. This is required to set the
// proper values in the JWE headers
type ByteWithECPublicKey struct {
ByteKey
PublicKey any
}
type ByteWithIVAndTag struct {
ByteKey
IV []byte
Tag []byte
}
type ByteWithSaltAndCount struct {
ByteKey
Salt []byte
Count int
}
// ByteSource is an interface for things that return a byte sequence.
// This is used for KeyGenerator so that the result of computations can
// carry more than just the generate byte sequence.
type ByteSource interface {
Bytes() []byte
}
type Setter interface {
Set(string, any) error
}
@@ -0,0 +1,139 @@
package keygen
import (
"crypto"
"crypto/ecdh"
"crypto/ecdsa"
"crypto/rand"
"encoding/binary"
"fmt"
"io"
"github.com/lestrrat-go/jwx/v3/internal/ecutil"
"github.com/lestrrat-go/jwx/v3/internal/tokens"
"github.com/lestrrat-go/jwx/v3/jwe/internal/concatkdf"
"github.com/lestrrat-go/jwx/v3/jwk"
)
// Bytes returns the byte from this ByteKey
func (k ByteKey) Bytes() []byte {
return []byte(k)
}
func Random(n int) (ByteSource, error) {
buf := make([]byte, n)
if _, err := io.ReadFull(rand.Reader, buf); err != nil {
return nil, fmt.Errorf(`failed to read from rand.Reader: %w`, err)
}
return ByteKey(buf), nil
}
// Ecdhes generates a new key using ECDH-ES
func Ecdhes(alg string, enc string, keysize int, pubkey *ecdsa.PublicKey, apu, apv []byte) (ByteSource, error) {
priv, err := ecdsa.GenerateKey(pubkey.Curve, rand.Reader)
if err != nil {
return nil, fmt.Errorf(`failed to generate key for ECDH-ES: %w`, err)
}
var algorithm string
if alg == tokens.ECDH_ES {
algorithm = enc
} else {
algorithm = alg
}
pubinfo := make([]byte, 4)
binary.BigEndian.PutUint32(pubinfo, uint32(keysize)*8)
if !priv.PublicKey.Curve.IsOnCurve(pubkey.X, pubkey.Y) {
return nil, fmt.Errorf(`public key used does not contain a point (X,Y) on the curve`)
}
z, _ := priv.PublicKey.Curve.ScalarMult(pubkey.X, pubkey.Y, priv.D.Bytes())
zBytes := ecutil.AllocECPointBuffer(z, priv.PublicKey.Curve)
defer ecutil.ReleaseECPointBuffer(zBytes)
kdf := concatkdf.New(crypto.SHA256, []byte(algorithm), zBytes, apu, apv, pubinfo, []byte{})
kek := make([]byte, keysize)
if _, err := kdf.Read(kek); err != nil {
return nil, fmt.Errorf(`failed to read kdf: %w`, err)
}
return ByteWithECPublicKey{
PublicKey: &priv.PublicKey,
ByteKey: ByteKey(kek),
}, nil
}
// X25519 generates a new key using ECDH-ES with X25519
func X25519(alg string, enc string, keysize int, pubkey *ecdh.PublicKey) (ByteSource, error) {
priv, err := ecdh.X25519().GenerateKey(rand.Reader)
if err != nil {
return nil, fmt.Errorf(`failed to generate key for X25519: %w`, err)
}
var algorithm string
if alg == tokens.ECDH_ES {
algorithm = enc
} else {
algorithm = alg
}
pubinfo := make([]byte, 4)
binary.BigEndian.PutUint32(pubinfo, uint32(keysize)*8)
zBytes, err := priv.ECDH(pubkey)
if err != nil {
return nil, fmt.Errorf(`failed to compute Z: %w`, err)
}
kdf := concatkdf.New(crypto.SHA256, []byte(algorithm), zBytes, []byte{}, []byte{}, pubinfo, []byte{})
kek := make([]byte, keysize)
if _, err := kdf.Read(kek); err != nil {
return nil, fmt.Errorf(`failed to read kdf: %w`, err)
}
return ByteWithECPublicKey{
PublicKey: priv.PublicKey(),
ByteKey: ByteKey(kek),
}, nil
}
// HeaderPopulate populates the header with the required EC-DSA public key
// information ('epk' key)
func (k ByteWithECPublicKey) Populate(h Setter) error {
key, err := jwk.Import(k.PublicKey)
if err != nil {
return fmt.Errorf(`failed to create JWK: %w`, err)
}
if err := h.Set("epk", key); err != nil {
return fmt.Errorf(`failed to write header: %w`, err)
}
return nil
}
// HeaderPopulate populates the header with the required AES GCM
// parameters ('iv' and 'tag')
func (k ByteWithIVAndTag) Populate(h Setter) error {
if err := h.Set("iv", k.IV); err != nil {
return fmt.Errorf(`failed to write header: %w`, err)
}
if err := h.Set("tag", k.Tag); err != nil {
return fmt.Errorf(`failed to write header: %w`, err)
}
return nil
}
// HeaderPopulate populates the header with the required PBES2
// parameters ('p2s' and 'p2c')
func (k ByteWithSaltAndCount) Populate(h Setter) error {
if err := h.Set("p2c", k.Count); err != nil {
return fmt.Errorf(`failed to write header: %w`, err)
}
if err := h.Set("p2s", k.Salt); err != nil {
return fmt.Errorf(`failed to write header: %w`, err)
}
return nil
}
+36
View File
@@ -0,0 +1,36 @@
// Code generated by tools/cmd/genreadfile/main.go. DO NOT EDIT.
package jwe
import (
"fmt"
"io/fs"
"os"
)
type sysFS struct{}
func (sysFS) Open(path string) (fs.File, error) {
return os.Open(path)
}
func ReadFile(path string, options ...ReadFileOption) (*Message, error) {
var srcFS fs.FS = sysFS{}
for _, option := range options {
switch option.Ident() {
case identFS{}:
if err := option.Value(&srcFS); err != nil {
return nil, fmt.Errorf("failed to set fs.FS: %w", err)
}
}
}
f, err := srcFS.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return ParseReader(f)
}
File diff suppressed because it is too large Load Diff
+43
View File
@@ -0,0 +1,43 @@
load("@rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "jwebb",
srcs = [
"content_cipher.go",
"key_decrypt_asymmetric.go",
"key_decrypt_symmetric.go",
"key_encrypt_asymmetric.go",
"key_encrypt_symmetric.go",
"key_encryption.go",
"keywrap.go",
],
importpath = "github.com/lestrrat-go/jwx/v3/jwe/jwebb",
visibility = ["//jwe:__subpackages__"],
deps = [
"//internal/keyconv",
"//internal/pool",
"//jwe/internal/cipher",
"//jwe/internal/concatkdf",
"//jwe/internal/content_crypt",
"//jwe/internal/keygen",
"//internal/tokens",
"@org_golang_x_crypto//pbkdf2",
],
)
go_test(
name = "jwebb_test",
srcs = [
"decrypt_test.go",
"jwebb_test.go",
"keywrap_test.go",
],
embed = [":jwebb"],
deps = [
"//internal/jwxtest",
"//jwa",
"//jwe/internal/keygen",
"//internal/tokens",
"@com_github_stretchr_testify//require",
],
)
@@ -0,0 +1,34 @@
package jwebb
import (
"fmt"
"github.com/lestrrat-go/jwx/v3/internal/tokens"
"github.com/lestrrat-go/jwx/v3/jwe/internal/cipher"
"github.com/lestrrat-go/jwx/v3/jwe/internal/content_crypt"
)
// ContentEncryptionIsSupported checks if the content encryption algorithm is supported
func ContentEncryptionIsSupported(alg string) bool {
switch alg {
case tokens.A128GCM, tokens.A192GCM, tokens.A256GCM,
tokens.A128CBC_HS256, tokens.A192CBC_HS384, tokens.A256CBC_HS512:
return true
default:
return false
}
}
// CreateContentCipher creates a content encryption cipher for the given algorithm string
func CreateContentCipher(alg string) (content_crypt.Cipher, error) {
if !ContentEncryptionIsSupported(alg) {
return nil, fmt.Errorf(`invalid content cipher algorithm (%s)`, alg)
}
cipher, err := cipher.NewAES(alg)
if err != nil {
return nil, fmt.Errorf(`failed to build content cipher for %s: %w`, alg, err)
}
return cipher, nil
}
+15
View File
@@ -0,0 +1,15 @@
// Package jwebb provides the building blocks (hence the name "bb") for JWE operations.
// It should be thought of as a low-level API, almost akin to internal packages
// that should not be used directly by users of the jwx package. However, these exist
// to provide a more efficient way to perform JWE operations without the overhead of
// the higher-level jwe package to power-users who know what they are doing.
//
// This package is currently considered EXPERIMENTAL, and the API may change
// without notice. It is not recommended to use this package unless you are
// fully aware of the implications of using it.
//
// All bb packages in jwx follow the same design principles:
// 1. Does minimal checking of input parameters (for performance); callers need to ensure that the parameters are valid.
// 2. All exported functions are stringly typed (i.e. they do not take any parameters unless they absolutely have to).
// 3. Does not rely on other public jwx packages (they are standalone, except for internal packages).
package jwebb
@@ -0,0 +1,177 @@
package jwebb
import (
"crypto"
"crypto/aes"
"crypto/ecdh"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/binary"
"fmt"
"hash"
"github.com/lestrrat-go/jwx/v3/internal/keyconv"
"github.com/lestrrat-go/jwx/v3/internal/tokens"
"github.com/lestrrat-go/jwx/v3/jwe/internal/concatkdf"
"github.com/lestrrat-go/jwx/v3/jwe/internal/keygen"
)
func contentEncryptionKeySize(ctalg string) (uint32, error) {
switch ctalg {
case tokens.A128GCM:
return tokens.KeySize16, nil
case tokens.A192GCM:
return tokens.KeySize24, nil
case tokens.A256GCM:
return tokens.KeySize32, nil
case tokens.A128CBC_HS256:
return tokens.KeySize32, nil
case tokens.A192CBC_HS384:
return tokens.KeySize48, nil
case tokens.A256CBC_HS512:
return tokens.KeySize64, nil
default:
return 0, fmt.Errorf(`unsupported content encryption algorithm %s`, ctalg)
}
}
func KeyEncryptionECDHESKeySize(alg, ctalg string) (string, uint32, bool, error) {
switch alg {
case tokens.ECDH_ES:
keysize, err := contentEncryptionKeySize(ctalg)
if err != nil {
return "", 0, false, err
}
return ctalg, keysize, false, nil
case tokens.ECDH_ES_A128KW:
return alg, tokens.KeySize16, true, nil
case tokens.ECDH_ES_A192KW:
return alg, tokens.KeySize24, true, nil
case tokens.ECDH_ES_A256KW:
return alg, tokens.KeySize32, true, nil
default:
return "", 0, false, fmt.Errorf(`unsupported key encryption algorithm %s`, alg)
}
}
func DeriveECDHES(alg string, apu, apv []byte, privkeyif, pubkeyif any, keysize uint32) ([]byte, error) {
pubinfo := make([]byte, 4)
binary.BigEndian.PutUint32(pubinfo, keysize*tokens.BitsPerByte)
var privkey *ecdh.PrivateKey
var pubkey *ecdh.PublicKey
if err := keyconv.ECDHPrivateKey(&privkey, privkeyif); err != nil {
return nil, fmt.Errorf(`jwebb.DeriveECDHES: %w`, err)
}
if err := keyconv.ECDHPublicKey(&pubkey, pubkeyif); err != nil {
return nil, fmt.Errorf(`jwebb.DeriveECDHES: %w`, err)
}
zBytes, err := privkey.ECDH(pubkey)
if err != nil {
return nil, fmt.Errorf(`jwebb.DeriveECDHES: unable to determine Z: %w`, err)
}
kdf := concatkdf.New(crypto.SHA256, []byte(alg), zBytes, apu, apv, pubinfo, []byte{})
key := make([]byte, keysize)
if _, err := kdf.Read(key); err != nil {
return nil, fmt.Errorf(`jwebb.DeriveECDHES: failed to read kdf: %w`, err)
}
return key, nil
}
func KeyDecryptECDHESKeyWrap(_, enckey []byte, alg string, apu, apv []byte, privkey, pubkey any, keysize uint32) ([]byte, error) {
key, err := DeriveECDHES(alg, apu, apv, privkey, pubkey, keysize)
if err != nil {
return nil, fmt.Errorf(`failed to derive ECDHES encryption key: %w`, err)
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, fmt.Errorf(`failed to create cipher for ECDH-ES key wrap: %w`, err)
}
return Unwrap(block, enckey)
}
func KeyDecryptECDHES(_, _ []byte, alg string, apu, apv []byte, privkey, pubkey any, keysize uint32) ([]byte, error) {
key, err := DeriveECDHES(alg, apu, apv, privkey, pubkey, keysize)
if err != nil {
return nil, fmt.Errorf(`failed to derive ECDHES encryption key: %w`, err)
}
return key, nil
}
// RSA key decryption functions
func KeyDecryptRSA15(_, enckey []byte, privkeyif any, keysize int) ([]byte, error) {
var privkey *rsa.PrivateKey
if err := keyconv.RSAPrivateKey(&privkey, privkeyif); err != nil {
return nil, fmt.Errorf(`jwebb.KeyDecryptRSA15: %w`, err)
}
// Perform some input validation.
expectedlen := privkey.PublicKey.N.BitLen() / tokens.BitsPerByte
if expectedlen != len(enckey) {
// Input size is incorrect, the encrypted payload should always match
// the size of the public modulus (e.g. using a 2048 bit key will
// produce 256 bytes of output). Reject this since it's invalid input.
return nil, fmt.Errorf(
"input size for key decrypt is incorrect (expected %d, got %d)",
expectedlen,
len(enckey),
)
}
// Generate a random CEK of the required size
bk, err := keygen.Random(keysize * tokens.RSAKeyGenMultiplier)
if err != nil {
return nil, fmt.Errorf(`failed to generate key`)
}
cek := bk.Bytes()
// Use a defer/recover pattern to handle potential panics from DecryptPKCS1v15SessionKey
defer func() {
// DecryptPKCS1v15SessionKey sometimes panics on an invalid payload
// because of an index out of bounds error, which we want to ignore.
// This has been fixed in Go 1.3.1 (released 2014/08/13), the recover()
// only exists for preventing crashes with unpatched versions.
// See: https://groups.google.com/forum/#!topic/golang-dev/7ihX6Y6kx9k
// See: https://code.google.com/p/go/source/detail?r=58ee390ff31602edb66af41ed10901ec95904d33
_ = recover()
}()
// When decrypting an RSA-PKCS1v1.5 payload, we must take precautions to
// prevent chosen-ciphertext attacks as described in RFC 3218, "Preventing
// the Million Message Attack on Cryptographic Message Syntax". We are
// therefore deliberately ignoring errors here.
_ = rsa.DecryptPKCS1v15SessionKey(rand.Reader, privkey, enckey, cek)
return cek, nil
}
func KeyDecryptRSAOAEP(_, enckey []byte, alg string, privkeyif any) ([]byte, error) {
var privkey *rsa.PrivateKey
if err := keyconv.RSAPrivateKey(&privkey, privkeyif); err != nil {
return nil, fmt.Errorf(`jwebb.KeyDecryptRSAOAEP: %w`, err)
}
var hash hash.Hash
switch alg {
case tokens.RSA_OAEP:
hash = sha1.New()
case tokens.RSA_OAEP_256:
hash = sha256.New()
case tokens.RSA_OAEP_384:
hash = sha512.New384()
case tokens.RSA_OAEP_512:
hash = sha512.New()
default:
return nil, fmt.Errorf(`failed to generate key encrypter for RSA-OAEP: RSA_OAEP/RSA_OAEP_256/RSA_OAEP_384/RSA_OAEP_512 required`)
}
return rsa.DecryptOAEP(hash, rand.Reader, privkey, enckey, []byte{})
}
@@ -0,0 +1,91 @@
package jwebb
import (
"crypto/aes"
cryptocipher "crypto/cipher"
"crypto/sha256"
"crypto/sha512"
"fmt"
"hash"
"golang.org/x/crypto/pbkdf2"
"github.com/lestrrat-go/jwx/v3/internal/tokens"
)
// AES key wrap decryption functions
// Use constants from tokens package
// No need to redefine them here
func KeyDecryptAESKW(_, enckey []byte, _ string, sharedkey []byte) ([]byte, error) {
block, err := aes.NewCipher(sharedkey)
if err != nil {
return nil, fmt.Errorf(`failed to create cipher from shared key: %w`, err)
}
cek, err := Unwrap(block, enckey)
if err != nil {
return nil, fmt.Errorf(`failed to unwrap data: %w`, err)
}
return cek, nil
}
func KeyDecryptDirect(_, _ []byte, _ string, cek []byte) ([]byte, error) {
return cek, nil
}
func KeyDecryptPBES2(_, enckey []byte, alg string, password []byte, salt []byte, count int) ([]byte, error) {
var hashFunc func() hash.Hash
var keylen int
switch alg {
case tokens.PBES2_HS256_A128KW:
hashFunc = sha256.New
keylen = tokens.KeySize16
case tokens.PBES2_HS384_A192KW:
hashFunc = sha512.New384
keylen = tokens.KeySize24
case tokens.PBES2_HS512_A256KW:
hashFunc = sha512.New
keylen = tokens.KeySize32
default:
return nil, fmt.Errorf(`unsupported PBES2 algorithm: %s`, alg)
}
// Derive key using PBKDF2
derivedKey := pbkdf2.Key(password, salt, count, keylen, hashFunc)
// Use the derived key for AES key wrap
return KeyDecryptAESKW(nil, enckey, alg, derivedKey)
}
func KeyDecryptAESGCMKW(recipientKey, _ []byte, _ string, sharedkey []byte, iv []byte, tag []byte) ([]byte, error) {
if len(iv) != tokens.GCMIVSize {
return nil, fmt.Errorf("GCM requires 96-bit iv, got %d", len(iv)*tokens.BitsPerByte)
}
if len(tag) != tokens.GCMTagSize {
return nil, fmt.Errorf("GCM requires 128-bit tag, got %d", len(tag)*tokens.BitsPerByte)
}
block, err := aes.NewCipher(sharedkey)
if err != nil {
return nil, fmt.Errorf(`failed to create new AES cipher: %w`, err)
}
aesgcm, err := cryptocipher.NewGCM(block)
if err != nil {
return nil, fmt.Errorf(`failed to create new GCM wrap: %w`, err)
}
// Combine recipient key and tag for GCM decryption
ciphertext := recipientKey[:]
ciphertext = append(ciphertext, tag...)
jek, err := aesgcm.Open(nil, iv, ciphertext, nil)
if err != nil {
return nil, fmt.Errorf(`failed to decode key: %w`, err)
}
return jek, nil
}
@@ -0,0 +1,147 @@
package jwebb
import (
"crypto/aes"
"crypto/ecdh"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"fmt"
"hash"
"github.com/lestrrat-go/jwx/v3/internal/tokens"
"github.com/lestrrat-go/jwx/v3/jwe/internal/keygen"
)
// KeyEncryptRSA15 encrypts the CEK using RSA PKCS#1 v1.5
func KeyEncryptRSA15(cek []byte, _ string, pubkey *rsa.PublicKey) (keygen.ByteSource, error) {
encrypted, err := rsa.EncryptPKCS1v15(rand.Reader, pubkey, cek)
if err != nil {
return nil, fmt.Errorf(`failed to encrypt using PKCS1v15: %w`, err)
}
return keygen.ByteKey(encrypted), nil
}
// KeyEncryptRSAOAEP encrypts the CEK using RSA OAEP
func KeyEncryptRSAOAEP(cek []byte, alg string, pubkey *rsa.PublicKey) (keygen.ByteSource, error) {
var hash hash.Hash
switch alg {
case tokens.RSA_OAEP:
hash = sha1.New()
case tokens.RSA_OAEP_256:
hash = sha256.New()
case tokens.RSA_OAEP_384:
hash = sha512.New384()
case tokens.RSA_OAEP_512:
hash = sha512.New()
default:
return nil, fmt.Errorf(`failed to generate key encrypter for RSA-OAEP: RSA_OAEP/RSA_OAEP_256/RSA_OAEP_384/RSA_OAEP_512 required`)
}
encrypted, err := rsa.EncryptOAEP(hash, rand.Reader, pubkey, cek, []byte{})
if err != nil {
return nil, fmt.Errorf(`failed to OAEP encrypt: %w`, err)
}
return keygen.ByteKey(encrypted), nil
}
// generateECDHESKeyECDSA generates the key material for ECDSA keys using ECDH-ES
func generateECDHESKeyECDSA(alg string, calg string, keysize uint32, pubkey *ecdsa.PublicKey, apu, apv []byte) (keygen.ByteWithECPublicKey, error) {
// Generate the key directly
kg, err := keygen.Ecdhes(alg, calg, int(keysize), pubkey, apu, apv)
if err != nil {
return keygen.ByteWithECPublicKey{}, fmt.Errorf(`failed to generate ECDSA key: %w`, err)
}
bwpk, ok := kg.(keygen.ByteWithECPublicKey)
if !ok {
return keygen.ByteWithECPublicKey{}, fmt.Errorf(`key generator generated invalid key (expected ByteWithECPublicKey)`)
}
return bwpk, nil
}
// generateECDHESKeyX25519 generates the key material for X25519 keys using ECDH-ES
func generateECDHESKeyX25519(alg string, calg string, keysize uint32, pubkey *ecdh.PublicKey) (keygen.ByteWithECPublicKey, error) {
// Generate the key directly
kg, err := keygen.X25519(alg, calg, int(keysize), pubkey)
if err != nil {
return keygen.ByteWithECPublicKey{}, fmt.Errorf(`failed to generate X25519 key: %w`, err)
}
bwpk, ok := kg.(keygen.ByteWithECPublicKey)
if !ok {
return keygen.ByteWithECPublicKey{}, fmt.Errorf(`key generator generated invalid key (expected ByteWithECPublicKey)`)
}
return bwpk, nil
}
// KeyEncryptECDHESKeyWrapECDSA encrypts the CEK using ECDH-ES with key wrapping for ECDSA keys
func KeyEncryptECDHESKeyWrapECDSA(cek []byte, alg string, apu, apv []byte, pubkey *ecdsa.PublicKey, keysize uint32, calg string) (keygen.ByteSource, error) {
bwpk, err := generateECDHESKeyECDSA(alg, calg, keysize, pubkey, apu, apv)
if err != nil {
return nil, err
}
// For key wrapping algorithms, wrap the CEK with the generated key
block, err := aes.NewCipher(bwpk.Bytes())
if err != nil {
return nil, fmt.Errorf(`failed to generate cipher from generated key: %w`, err)
}
jek, err := Wrap(block, cek)
if err != nil {
return nil, fmt.Errorf(`failed to wrap data: %w`, err)
}
bwpk.ByteKey = keygen.ByteKey(jek)
return bwpk, nil
}
// KeyEncryptECDHESKeyWrapX25519 encrypts the CEK using ECDH-ES with key wrapping for X25519 keys
func KeyEncryptECDHESKeyWrapX25519(cek []byte, alg string, _ []byte, _ []byte, pubkey *ecdh.PublicKey, keysize uint32, calg string) (keygen.ByteSource, error) {
bwpk, err := generateECDHESKeyX25519(alg, calg, keysize, pubkey)
if err != nil {
return nil, err
}
// For key wrapping algorithms, wrap the CEK with the generated key
block, err := aes.NewCipher(bwpk.Bytes())
if err != nil {
return nil, fmt.Errorf(`failed to generate cipher from generated key: %w`, err)
}
jek, err := Wrap(block, cek)
if err != nil {
return nil, fmt.Errorf(`failed to wrap data: %w`, err)
}
bwpk.ByteKey = keygen.ByteKey(jek)
return bwpk, nil
}
// KeyEncryptECDHESECDSA encrypts using ECDH-ES direct (no key wrapping) for ECDSA keys
func KeyEncryptECDHESECDSA(_ []byte, alg string, apu, apv []byte, pubkey *ecdsa.PublicKey, keysize uint32, calg string) (keygen.ByteSource, error) {
bwpk, err := generateECDHESKeyECDSA(alg, calg, keysize, pubkey, apu, apv)
if err != nil {
return nil, err
}
// For direct ECDH-ES, return the generated key directly
return bwpk, nil
}
// KeyEncryptECDHESX25519 encrypts using ECDH-ES direct (no key wrapping) for X25519 keys
func KeyEncryptECDHESX25519(_ []byte, alg string, _, _ []byte, pubkey *ecdh.PublicKey, keysize uint32, calg string) (keygen.ByteSource, error) {
bwpk, err := generateECDHESKeyX25519(alg, calg, keysize, pubkey)
if err != nil {
return nil, err
}
// For direct ECDH-ES, return the generated key directly
return bwpk, nil
}
@@ -0,0 +1,115 @@
package jwebb
import (
"crypto/aes"
cryptocipher "crypto/cipher"
"crypto/rand"
"crypto/sha256"
"crypto/sha512"
"fmt"
"hash"
"io"
"golang.org/x/crypto/pbkdf2"
"github.com/lestrrat-go/jwx/v3/internal/tokens"
"github.com/lestrrat-go/jwx/v3/jwe/internal/keygen"
)
// KeyEncryptAESKW encrypts the CEK using AES key wrap
func KeyEncryptAESKW(cek []byte, _ string, sharedkey []byte) (keygen.ByteSource, error) {
block, err := aes.NewCipher(sharedkey)
if err != nil {
return nil, fmt.Errorf(`failed to create cipher from shared key: %w`, err)
}
encrypted, err := Wrap(block, cek)
if err != nil {
return nil, fmt.Errorf(`failed to wrap data: %w`, err)
}
return keygen.ByteKey(encrypted), nil
}
// KeyEncryptDirect returns the CEK directly for DIRECT algorithm
func KeyEncryptDirect(_ []byte, _ string, sharedkey []byte) (keygen.ByteSource, error) {
return keygen.ByteKey(sharedkey), nil
}
// KeyEncryptPBES2 encrypts the CEK using PBES2 password-based encryption
func KeyEncryptPBES2(cek []byte, alg string, password []byte) (keygen.ByteSource, error) {
var hashFunc func() hash.Hash
var keylen int
switch alg {
case tokens.PBES2_HS256_A128KW:
hashFunc = sha256.New
keylen = tokens.KeySize16
case tokens.PBES2_HS384_A192KW:
hashFunc = sha512.New384
keylen = tokens.KeySize24
case tokens.PBES2_HS512_A256KW:
hashFunc = sha512.New
keylen = tokens.KeySize32
default:
return nil, fmt.Errorf(`unsupported PBES2 algorithm: %s`, alg)
}
count := tokens.PBES2DefaultIterations
salt := make([]byte, keylen)
_, err := io.ReadFull(rand.Reader, salt)
if err != nil {
return nil, fmt.Errorf(`failed to get random salt: %w`, err)
}
fullsalt := []byte(alg)
fullsalt = append(fullsalt, byte(tokens.PBES2NullByteSeparator))
fullsalt = append(fullsalt, salt...)
// Derive key using PBKDF2
derivedKey := pbkdf2.Key(password, fullsalt, count, keylen, hashFunc)
// Use the derived key for AES key wrap
block, err := aes.NewCipher(derivedKey)
if err != nil {
return nil, fmt.Errorf(`failed to create cipher from derived key: %w`, err)
}
encrypted, err := Wrap(block, cek)
if err != nil {
return nil, fmt.Errorf(`failed to wrap data: %w`, err)
}
return keygen.ByteWithSaltAndCount{
ByteKey: encrypted,
Salt: salt,
Count: count,
}, nil
}
// KeyEncryptAESGCMKW encrypts the CEK using AES GCM key wrap
func KeyEncryptAESGCMKW(cek []byte, _ string, sharedkey []byte) (keygen.ByteSource, error) {
block, err := aes.NewCipher(sharedkey)
if err != nil {
return nil, fmt.Errorf(`failed to create new AES cipher: %w`, err)
}
aesgcm, err := cryptocipher.NewGCM(block)
if err != nil {
return nil, fmt.Errorf(`failed to create new GCM wrap: %w`, err)
}
iv := make([]byte, aesgcm.NonceSize())
_, err = io.ReadFull(rand.Reader, iv)
if err != nil {
return nil, fmt.Errorf(`failed to get random iv: %w`, err)
}
encrypted := aesgcm.Seal(nil, iv, cek, nil)
tag := encrypted[len(encrypted)-aesgcm.Overhead():]
ciphertext := encrypted[:len(encrypted)-aesgcm.Overhead()]
return keygen.ByteWithIVAndTag{
ByteKey: ciphertext,
IV: iv,
Tag: tag,
}, nil
}
@@ -0,0 +1,70 @@
package jwebb
import (
"github.com/lestrrat-go/jwx/v3/internal/tokens"
)
// IsECDHES checks if the algorithm is an ECDH-ES based algorithm
func IsECDHES(alg string) bool {
switch alg {
case tokens.ECDH_ES, tokens.ECDH_ES_A128KW, tokens.ECDH_ES_A192KW, tokens.ECDH_ES_A256KW:
return true
default:
return false
}
}
// IsRSA15 checks if the algorithm is RSA1_5
func IsRSA15(alg string) bool {
return alg == tokens.RSA1_5
}
// IsRSAOAEP checks if the algorithm is an RSA-OAEP based algorithm
func IsRSAOAEP(alg string) bool {
switch alg {
case tokens.RSA_OAEP, tokens.RSA_OAEP_256, tokens.RSA_OAEP_384, tokens.RSA_OAEP_512:
return true
default:
return false
}
}
// IsAESKW checks if the algorithm is an AES key wrap algorithm
func IsAESKW(alg string) bool {
switch alg {
case tokens.A128KW, tokens.A192KW, tokens.A256KW:
return true
default:
return false
}
}
// IsAESGCMKW checks if the algorithm is an AES-GCM key wrap algorithm
func IsAESGCMKW(alg string) bool {
switch alg {
case tokens.A128GCMKW, tokens.A192GCMKW, tokens.A256GCMKW:
return true
default:
return false
}
}
// IsPBES2 checks if the algorithm is a PBES2 based algorithm
func IsPBES2(alg string) bool {
switch alg {
case tokens.PBES2_HS256_A128KW, tokens.PBES2_HS384_A192KW, tokens.PBES2_HS512_A256KW:
return true
default:
return false
}
}
// IsDirect checks if the algorithm is direct encryption
func IsDirect(alg string) bool {
return alg == tokens.DIRECT
}
// IsSymmetric checks if the algorithm is a symmetric key encryption algorithm
func IsSymmetric(alg string) bool {
return IsAESKW(alg) || IsAESGCMKW(alg) || IsPBES2(alg) || IsDirect(alg)
}
+110
View File
@@ -0,0 +1,110 @@
package jwebb
import (
"crypto/cipher"
"crypto/subtle"
"encoding/binary"
"fmt"
"github.com/lestrrat-go/jwx/v3/internal/pool"
"github.com/lestrrat-go/jwx/v3/internal/tokens"
)
var keywrapDefaultIV = []byte{0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6}
func Wrap(kek cipher.Block, cek []byte) ([]byte, error) {
if len(cek)%tokens.KeywrapBlockSize != 0 {
return nil, fmt.Errorf(`keywrap input must be %d byte blocks`, tokens.KeywrapBlockSize)
}
n := len(cek) / tokens.KeywrapChunkLen
r := make([][]byte, n)
for i := range n {
r[i] = make([]byte, tokens.KeywrapChunkLen)
copy(r[i], cek[i*tokens.KeywrapChunkLen:])
}
buffer := pool.ByteSlice().GetCapacity(tokens.KeywrapChunkLen * 2)
defer pool.ByteSlice().Put(buffer)
// the byte slice has the capacity, but len is 0
buffer = buffer[:tokens.KeywrapChunkLen*2]
tBytes := pool.ByteSlice().GetCapacity(tokens.KeywrapChunkLen)
defer pool.ByteSlice().Put(tBytes)
// the byte slice has the capacity, but len is 0
tBytes = tBytes[:tokens.KeywrapChunkLen]
copy(buffer, keywrapDefaultIV)
for t := range tokens.KeywrapRounds * n {
copy(buffer[tokens.KeywrapChunkLen:], r[t%n])
kek.Encrypt(buffer, buffer)
binary.BigEndian.PutUint64(tBytes, uint64(t+1))
for i := range tokens.KeywrapChunkLen {
buffer[i] = buffer[i] ^ tBytes[i]
}
copy(r[t%n], buffer[tokens.KeywrapChunkLen:])
}
out := make([]byte, (n+1)*tokens.KeywrapChunkLen)
copy(out, buffer[:tokens.KeywrapChunkLen])
for i := range r {
copy(out[(i+1)*tokens.KeywrapBlockSize:], r[i])
}
return out, nil
}
func Unwrap(block cipher.Block, ciphertxt []byte) ([]byte, error) {
if len(ciphertxt)%tokens.KeywrapChunkLen != 0 {
return nil, fmt.Errorf(`keyunwrap input must be %d byte blocks`, tokens.KeywrapChunkLen)
}
n := (len(ciphertxt) / tokens.KeywrapChunkLen) - 1
r := make([][]byte, n)
for i := range r {
r[i] = make([]byte, tokens.KeywrapChunkLen)
copy(r[i], ciphertxt[(i+1)*tokens.KeywrapChunkLen:])
}
buffer := pool.ByteSlice().GetCapacity(tokens.KeywrapChunkLen * 2)
defer pool.ByteSlice().Put(buffer)
// the byte slice has the capacity, but len is 0
buffer = buffer[:tokens.KeywrapChunkLen*2]
tBytes := pool.ByteSlice().GetCapacity(tokens.KeywrapChunkLen)
defer pool.ByteSlice().Put(tBytes)
// the byte slice has the capacity, but len is 0
tBytes = tBytes[:tokens.KeywrapChunkLen]
copy(buffer[:tokens.KeywrapChunkLen], ciphertxt[:tokens.KeywrapChunkLen])
for t := tokens.KeywrapRounds*n - 1; t >= 0; t-- {
binary.BigEndian.PutUint64(tBytes, uint64(t+1))
for i := range tokens.KeywrapChunkLen {
buffer[i] = buffer[i] ^ tBytes[i]
}
copy(buffer[tokens.KeywrapChunkLen:], r[t%n])
block.Decrypt(buffer, buffer)
copy(r[t%n], buffer[tokens.KeywrapChunkLen:])
}
if subtle.ConstantTimeCompare(buffer[:tokens.KeywrapChunkLen], keywrapDefaultIV) == 0 {
return nil, fmt.Errorf(`key unwrap: failed to unwrap key`)
}
out := make([]byte, n*tokens.KeywrapChunkLen)
for i := range r {
copy(out[i*tokens.KeywrapChunkLen:], r[i])
}
return out, nil
}
+163
View File
@@ -0,0 +1,163 @@
package jwe
import (
"context"
"fmt"
"sync"
"github.com/lestrrat-go/jwx/v3/jwa"
"github.com/lestrrat-go/jwx/v3/jwk"
)
// 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.Decrypt()` 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 CEK:
//
// for r in msg.Recipients {
// for kp in keyProviders {
// kp.FetchKeys(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 recipient, until
// a match is found:
//
// keys := sink.Keys()
// for key in keys {
// if decryptJWEKey(recipient.EncryptedKey(), key) {
// return OK
// }
// }
type KeyProvider interface {
FetchKeys(context.Context, KeySink, Recipient, *Message) error
}
// KeySink is a data storage where `jwe.KeyProvider` objects should
// send their keys to.
type KeySink interface {
Key(jwa.KeyEncryptionAlgorithm, any)
}
type algKeyPair struct {
alg jwa.KeyAlgorithm
key any
}
type algKeySink struct {
mu sync.Mutex
list []algKeyPair
}
func (s *algKeySink) Key(alg jwa.KeyEncryptionAlgorithm, key any) {
s.mu.Lock()
s.list = append(s.list, algKeyPair{alg, key})
s.mu.Unlock()
}
type staticKeyProvider struct {
alg jwa.KeyEncryptionAlgorithm
key any
}
func (kp *staticKeyProvider) FetchKeys(_ context.Context, sink KeySink, _ Recipient, _ *Message) error {
sink.Key(kp.alg, kp.key)
return nil
}
type keySetProvider struct {
set jwk.Set
requireKid bool
}
func (kp *keySetProvider) selectKey(sink KeySink, key jwk.Key, _ Recipient, _ *Message) error {
if usage, ok := key.KeyUsage(); ok {
if usage != "" && usage != jwk.ForEncryption.String() {
return nil
}
}
if v, ok := key.Algorithm(); ok {
kalg, ok := jwa.LookupKeyEncryptionAlgorithm(v.String())
if !ok {
return fmt.Errorf(`invalid key encryption algorithm %s`, v)
}
sink.Key(kalg, key)
return nil
}
return nil
}
func (kp *keySetProvider) FetchKeys(_ context.Context, sink KeySink, r Recipient, msg *Message) error {
if kp.requireKid {
var key jwk.Key
wantedKid, ok := r.Headers().KeyID()
if !ok || wantedKid == "" {
return fmt.Errorf(`failed to find matching key: no key ID ("kid") specified in token but multiple keys available in key set`)
}
// Otherwise we better be able to look up the key, baby.
v, ok := kp.set.LookupKeyID(wantedKid)
if !ok {
return fmt.Errorf(`failed to find key with key ID %q in key set`, wantedKid)
}
key = v
return kp.selectKey(sink, key, r, msg)
}
for i := range kp.set.Len() {
key, _ := kp.set.Key(i)
if err := kp.selectKey(sink, key, r, msg); err != nil {
continue
}
}
return nil
}
// KeyProviderFunc is a type of KeyProvider that is implemented by
// a single function. You can use this to create ad-hoc `KeyProvider`
// instances.
type KeyProviderFunc func(context.Context, KeySink, Recipient, *Message) error
func (kp KeyProviderFunc) FetchKeys(ctx context.Context, sink KeySink, r Recipient, msg *Message) error {
return kp(ctx, sink, r, msg)
}
+560
View File
@@ -0,0 +1,560 @@
package jwe
import (
"fmt"
"sort"
"strings"
"github.com/lestrrat-go/jwx/v3/internal/base64"
"github.com/lestrrat-go/jwx/v3/internal/json"
"github.com/lestrrat-go/jwx/v3/internal/pool"
"github.com/lestrrat-go/jwx/v3/internal/tokens"
)
// NewRecipient creates a Recipient object
func NewRecipient() Recipient {
return &stdRecipient{
headers: NewHeaders(),
}
}
func (r *stdRecipient) SetHeaders(h Headers) error {
r.headers = h
return nil
}
func (r *stdRecipient) SetEncryptedKey(v []byte) error {
r.encryptedKey = v
return nil
}
func (r *stdRecipient) Headers() Headers {
return r.headers
}
func (r *stdRecipient) EncryptedKey() []byte {
return r.encryptedKey
}
type recipientMarshalProxy struct {
Headers Headers `json:"header"`
EncryptedKey string `json:"encrypted_key"`
}
func (r *stdRecipient) UnmarshalJSON(buf []byte) error {
var proxy recipientMarshalProxy
proxy.Headers = NewHeaders()
if err := json.Unmarshal(buf, &proxy); err != nil {
return fmt.Errorf(`failed to unmarshal json into recipient: %w`, err)
}
r.headers = proxy.Headers
decoded, err := base64.DecodeString(proxy.EncryptedKey)
if err != nil {
return fmt.Errorf(`failed to decode "encrypted_key": %w`, err)
}
r.encryptedKey = decoded
return nil
}
func (r *stdRecipient) MarshalJSON() ([]byte, error) {
buf := pool.BytesBuffer().Get()
defer pool.BytesBuffer().Put(buf)
buf.WriteString(`{"header":`)
hdrbuf, err := json.Marshal(r.headers)
if err != nil {
return nil, fmt.Errorf(`failed to marshal recipient header: %w`, err)
}
buf.Write(hdrbuf)
buf.WriteString(`,"encrypted_key":"`)
buf.WriteString(base64.EncodeToString(r.encryptedKey))
buf.WriteString(`"}`)
ret := make([]byte, buf.Len())
copy(ret, buf.Bytes())
return ret, nil
}
// NewMessage creates a new message
func NewMessage() *Message {
return &Message{}
}
func (m *Message) AuthenticatedData() []byte {
return m.authenticatedData
}
func (m *Message) CipherText() []byte {
return m.cipherText
}
func (m *Message) InitializationVector() []byte {
return m.initializationVector
}
func (m *Message) Tag() []byte {
return m.tag
}
func (m *Message) ProtectedHeaders() Headers {
return m.protectedHeaders
}
func (m *Message) Recipients() []Recipient {
return m.recipients
}
func (m *Message) UnprotectedHeaders() Headers {
return m.unprotectedHeaders
}
const (
AuthenticatedDataKey = "aad"
CipherTextKey = "ciphertext"
CountKey = "p2c"
InitializationVectorKey = "iv"
ProtectedHeadersKey = "protected"
RecipientsKey = "recipients"
SaltKey = "p2s"
TagKey = "tag"
UnprotectedHeadersKey = "unprotected"
HeadersKey = "header"
EncryptedKeyKey = "encrypted_key"
)
func (m *Message) Set(k string, v any) error {
switch k {
case AuthenticatedDataKey:
buf, ok := v.([]byte)
if !ok {
return fmt.Errorf(`invalid value %T for %s key`, v, AuthenticatedDataKey)
}
m.authenticatedData = buf
case CipherTextKey:
buf, ok := v.([]byte)
if !ok {
return fmt.Errorf(`invalid value %T for %s key`, v, CipherTextKey)
}
m.cipherText = buf
case InitializationVectorKey:
buf, ok := v.([]byte)
if !ok {
return fmt.Errorf(`invalid value %T for %s key`, v, InitializationVectorKey)
}
m.initializationVector = buf
case ProtectedHeadersKey:
cv, ok := v.(Headers)
if !ok {
return fmt.Errorf(`invalid value %T for %s key`, v, ProtectedHeadersKey)
}
m.protectedHeaders = cv
case RecipientsKey:
cv, ok := v.([]Recipient)
if !ok {
return fmt.Errorf(`invalid value %T for %s key`, v, RecipientsKey)
}
m.recipients = cv
case TagKey:
buf, ok := v.([]byte)
if !ok {
return fmt.Errorf(`invalid value %T for %s key`, v, TagKey)
}
m.tag = buf
case UnprotectedHeadersKey:
cv, ok := v.(Headers)
if !ok {
return fmt.Errorf(`invalid value %T for %s key`, v, UnprotectedHeadersKey)
}
m.unprotectedHeaders = cv
default:
if m.unprotectedHeaders == nil {
m.unprotectedHeaders = NewHeaders()
}
return m.unprotectedHeaders.Set(k, v)
}
return nil
}
type messageMarshalProxy struct {
AuthenticatedData string `json:"aad,omitempty"`
CipherText string `json:"ciphertext"`
InitializationVector string `json:"iv,omitempty"`
ProtectedHeaders json.RawMessage `json:"protected"`
Recipients []json.RawMessage `json:"recipients,omitempty"`
Tag string `json:"tag,omitempty"`
UnprotectedHeaders Headers `json:"unprotected,omitempty"`
// For flattened structure. Headers is NOT a Headers type,
// so that we can detect its presence by checking proxy.Headers != nil
Headers json.RawMessage `json:"header,omitempty"`
EncryptedKey string `json:"encrypted_key,omitempty"`
}
type jsonKV struct {
Key string
Value string
}
func (m *Message) MarshalJSON() ([]byte, error) {
// This is slightly convoluted, but we need to encode the
// protected headers, so we do it by hand
buf := pool.BytesBuffer().Get()
defer pool.BytesBuffer().Put(buf)
enc := json.NewEncoder(buf)
var fields []jsonKV
if cipherText := m.CipherText(); len(cipherText) > 0 {
buf.Reset()
if err := enc.Encode(base64.EncodeToString(cipherText)); err != nil {
return nil, fmt.Errorf(`failed to encode %s field: %w`, CipherTextKey, err)
}
fields = append(fields, jsonKV{
Key: CipherTextKey,
Value: strings.TrimSpace(buf.String()),
})
}
if iv := m.InitializationVector(); len(iv) > 0 {
buf.Reset()
if err := enc.Encode(base64.EncodeToString(iv)); err != nil {
return nil, fmt.Errorf(`failed to encode %s field: %w`, InitializationVectorKey, err)
}
fields = append(fields, jsonKV{
Key: InitializationVectorKey,
Value: strings.TrimSpace(buf.String()),
})
}
var encodedProtectedHeaders []byte
if h := m.ProtectedHeaders(); h != nil {
v, err := h.Encode()
if err != nil {
return nil, fmt.Errorf(`failed to encode protected headers: %w`, err)
}
encodedProtectedHeaders = v
if len(encodedProtectedHeaders) <= 2 { // '{}'
encodedProtectedHeaders = nil
} else {
fields = append(fields, jsonKV{
Key: ProtectedHeadersKey,
Value: fmt.Sprintf("%q", encodedProtectedHeaders),
})
}
}
if aad := m.AuthenticatedData(); len(aad) > 0 {
aad = base64.Encode(aad)
if encodedProtectedHeaders != nil {
tmp := append(encodedProtectedHeaders, tokens.Period)
aad = append(tmp, aad...)
}
buf.Reset()
if err := enc.Encode(aad); err != nil {
return nil, fmt.Errorf(`failed to encode %s field: %w`, AuthenticatedDataKey, err)
}
fields = append(fields, jsonKV{
Key: AuthenticatedDataKey,
Value: strings.TrimSpace(buf.String()),
})
}
if recipients := m.Recipients(); len(recipients) > 0 {
if len(recipients) == 1 { // Use flattened format
if hdrs := recipients[0].Headers(); hdrs != nil {
var skipHeaders bool
if zeroer, ok := hdrs.(isZeroer); ok {
if zeroer.isZero() {
skipHeaders = true
}
}
if !skipHeaders {
buf.Reset()
if err := enc.Encode(hdrs); err != nil {
return nil, fmt.Errorf(`failed to encode %s field: %w`, HeadersKey, err)
}
fields = append(fields, jsonKV{
Key: HeadersKey,
Value: strings.TrimSpace(buf.String()),
})
}
}
if ek := recipients[0].EncryptedKey(); len(ek) > 0 {
buf.Reset()
if err := enc.Encode(base64.EncodeToString(ek)); err != nil {
return nil, fmt.Errorf(`failed to encode %s field: %w`, EncryptedKeyKey, err)
}
fields = append(fields, jsonKV{
Key: EncryptedKeyKey,
Value: strings.TrimSpace(buf.String()),
})
}
} else {
buf.Reset()
if err := enc.Encode(recipients); err != nil {
return nil, fmt.Errorf(`failed to encode %s field: %w`, RecipientsKey, err)
}
fields = append(fields, jsonKV{
Key: RecipientsKey,
Value: strings.TrimSpace(buf.String()),
})
}
}
if tag := m.Tag(); len(tag) > 0 {
buf.Reset()
if err := enc.Encode(base64.EncodeToString(tag)); err != nil {
return nil, fmt.Errorf(`failed to encode %s field: %w`, TagKey, err)
}
fields = append(fields, jsonKV{
Key: TagKey,
Value: strings.TrimSpace(buf.String()),
})
}
if h := m.UnprotectedHeaders(); h != nil {
unprotected, err := json.Marshal(h)
if err != nil {
return nil, fmt.Errorf(`failed to encode unprotected headers: %w`, err)
}
if len(unprotected) > 2 {
fields = append(fields, jsonKV{
Key: UnprotectedHeadersKey,
Value: fmt.Sprintf("%q", unprotected),
})
}
}
sort.Slice(fields, func(i, j int) bool {
return fields[i].Key < fields[j].Key
})
buf.Reset()
fmt.Fprintf(buf, `{`)
for i, kv := range fields {
if i > 0 {
fmt.Fprintf(buf, `,`)
}
fmt.Fprintf(buf, `%q:%s`, kv.Key, kv.Value)
}
fmt.Fprintf(buf, `}`)
ret := make([]byte, buf.Len())
copy(ret, buf.Bytes())
return ret, nil
}
func (m *Message) UnmarshalJSON(buf []byte) error {
var proxy messageMarshalProxy
proxy.UnprotectedHeaders = NewHeaders()
if err := json.Unmarshal(buf, &proxy); err != nil {
return fmt.Errorf(`failed to unmashal JSON into message: %w`, err)
}
// Get the string value
var protectedHeadersStr string
if err := json.Unmarshal(proxy.ProtectedHeaders, &protectedHeadersStr); err != nil {
return fmt.Errorf(`failed to decode protected headers (1): %w`, err)
}
// It's now in _quoted_ base64 string. Decode it
protectedHeadersRaw, err := base64.DecodeString(protectedHeadersStr)
if err != nil {
return fmt.Errorf(`failed to base64 decoded protected headers buffer: %w`, err)
}
h := NewHeaders()
if err := json.Unmarshal(protectedHeadersRaw, h); err != nil {
return fmt.Errorf(`failed to decode protected headers (2): %w`, err)
}
// if this were a flattened message, we would see a "header" and "ciphertext"
// field. TODO: do both of these conditions need to meet, or just one?
if proxy.Headers != nil || len(proxy.EncryptedKey) > 0 {
recipient := NewRecipient()
// `"heders"` could be empty. If that's the case, just skip the
// following unmarshaling step
if proxy.Headers != nil {
hdrs := NewHeaders()
if err := json.Unmarshal(proxy.Headers, hdrs); err != nil {
return fmt.Errorf(`failed to decode headers field: %w`, err)
}
if err := recipient.SetHeaders(hdrs); err != nil {
return fmt.Errorf(`failed to set new headers: %w`, err)
}
}
if v := proxy.EncryptedKey; len(v) > 0 {
buf, err := base64.DecodeString(v)
if err != nil {
return fmt.Errorf(`failed to decode encrypted key: %w`, err)
}
if err := recipient.SetEncryptedKey(buf); err != nil {
return fmt.Errorf(`failed to set encrypted key: %w`, err)
}
}
m.recipients = append(m.recipients, recipient)
} else {
for i, recipientbuf := range proxy.Recipients {
recipient := NewRecipient()
if err := json.Unmarshal(recipientbuf, recipient); err != nil {
return fmt.Errorf(`failed to decode recipient at index %d: %w`, i, err)
}
m.recipients = append(m.recipients, recipient)
}
}
if src := proxy.AuthenticatedData; len(src) > 0 {
v, err := base64.DecodeString(src)
if err != nil {
return fmt.Errorf(`failed to decode "aad": %w`, err)
}
m.authenticatedData = v
}
if src := proxy.CipherText; len(src) > 0 {
v, err := base64.DecodeString(src)
if err != nil {
return fmt.Errorf(`failed to decode "ciphertext": %w`, err)
}
m.cipherText = v
}
if src := proxy.InitializationVector; len(src) > 0 {
v, err := base64.DecodeString(src)
if err != nil {
return fmt.Errorf(`failed to decode "iv": %w`, err)
}
m.initializationVector = v
}
if src := proxy.Tag; len(src) > 0 {
v, err := base64.DecodeString(src)
if err != nil {
return fmt.Errorf(`failed to decode "tag": %w`, err)
}
m.tag = v
}
m.protectedHeaders = h
if m.storeProtectedHeaders {
// this is later used for decryption
m.rawProtectedHeaders = base64.Encode(protectedHeadersRaw)
}
if iz, ok := proxy.UnprotectedHeaders.(isZeroer); ok {
if !iz.isZero() {
m.unprotectedHeaders = proxy.UnprotectedHeaders
}
}
if len(m.recipients) == 0 {
if err := m.makeDummyRecipient(proxy.EncryptedKey, m.protectedHeaders); err != nil {
return fmt.Errorf(`failed to setup recipient: %w`, err)
}
}
return nil
}
func (m *Message) makeDummyRecipient(enckeybuf string, protected Headers) error {
// Recipients in this case should not contain the content encryption key,
// so move that out
hdrs, err := protected.Clone()
if err != nil {
return fmt.Errorf(`failed to clone headers: %w`, err)
}
if err := hdrs.Remove(ContentEncryptionKey); err != nil {
return fmt.Errorf(`failed to remove %#v from public header: %w`, ContentEncryptionKey, err)
}
enckey, err := base64.DecodeString(enckeybuf)
if err != nil {
return fmt.Errorf(`failed to decode encrypted key: %w`, err)
}
if err := m.Set(RecipientsKey, []Recipient{
&stdRecipient{
headers: hdrs,
encryptedKey: enckey,
},
}); err != nil {
return fmt.Errorf(`failed to set %s: %w`, RecipientsKey, err)
}
return nil
}
// 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 Compact(m *Message, _ ...CompactOption) ([]byte, error) {
if len(m.recipients) != 1 {
return nil, fmt.Errorf(`wrong number of recipients for compact serialization`)
}
recipient := m.recipients[0]
// The protected header must be a merge between the message-wide
// protected header AND the recipient header
// There's something wrong if m.protectedHeaders is nil, but
// it could happen
if m.protectedHeaders == nil {
return nil, fmt.Errorf(`invalid protected header`)
}
hcopy, err := m.protectedHeaders.Clone()
if err != nil {
return nil, fmt.Errorf(`failed to copy protected header: %w`, err)
}
hcopy, err = hcopy.Merge(m.unprotectedHeaders)
if err != nil {
return nil, fmt.Errorf(`failed to merge unprotected header: %w`, err)
}
hcopy, err = hcopy.Merge(recipient.Headers())
if err != nil {
return nil, fmt.Errorf(`failed to merge recipient header: %w`, err)
}
protected, err := hcopy.Encode()
if err != nil {
return nil, fmt.Errorf(`failed to encode header: %w`, err)
}
encryptedKey := base64.Encode(recipient.EncryptedKey())
iv := base64.Encode(m.initializationVector)
cipher := base64.Encode(m.cipherText)
tag := base64.Encode(m.tag)
buf := pool.BytesBuffer().Get()
defer pool.BytesBuffer().Put(buf)
buf.Grow(len(protected) + len(encryptedKey) + len(iv) + len(cipher) + len(tag) + 4)
buf.Write(protected)
buf.WriteByte(tokens.Period)
buf.Write(encryptedKey)
buf.WriteByte(tokens.Period)
buf.Write(iv)
buf.WriteByte(tokens.Period)
buf.Write(cipher)
buf.WriteByte(tokens.Period)
buf.Write(tag)
result := make([]byte, buf.Len())
copy(result, buf.Bytes())
return result, nil
}
+109
View File
@@ -0,0 +1,109 @@
package jwe
import (
"github.com/lestrrat-go/jwx/v3/jwa"
"github.com/lestrrat-go/jwx/v3/jwk"
"github.com/lestrrat-go/option/v2"
)
// WithProtectedHeaders is used to 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
func WithProtectedHeaders(h Headers) EncryptOption {
cloned, _ := h.Clone()
return &encryptOption{option.New(identProtectedHeaders{}, cloned)}
}
type withKey struct {
alg jwa.KeyAlgorithm
key any
headers Headers
}
type WithKeySuboption interface {
Option
withKeySuboption()
}
type withKeySuboption struct {
Option
}
func (*withKeySuboption) withKeySuboption() {}
// WithPerRecipientHeaders is used to pass header values for each recipient.
// Note that these headers are by definition _unprotected_.
func WithPerRecipientHeaders(hdr Headers) WithKeySuboption {
return &withKeySuboption{option.New(identPerRecipientHeaders{}, hdr)}
}
// 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.KeyEncryptionAlgorithm`
// 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.SignatureAlgorithm`,
// 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.
func WithKey(alg jwa.KeyAlgorithm, key any, options ...WithKeySuboption) EncryptDecryptOption {
var hdr Headers
for _, option := range options {
switch option.Ident() {
case identPerRecipientHeaders{}:
if err := option.Value(&hdr); err != nil {
panic(`jwe.WithKey() requires Headers value for WithPerRecipientHeaders option`)
}
}
}
return &encryptDecryptOption{option.New(identKey{}, &withKey{
alg: alg,
key: key,
headers: hdr,
})}
}
func WithKeySet(set jwk.Set, options ...WithKeySetSuboption) DecryptOption {
requireKid := true
for _, option := range options {
switch option.Ident() {
case identRequireKid{}:
if err := option.Value(&requireKid); err != nil {
panic(`jwe.WithKeySet() requires bool value for WithRequireKid option`)
}
}
}
return WithKeyProvider(&keySetProvider{
set: set,
requireKid: requireKid,
})
}
// 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 WithJSON(options ...WithJSONSuboption) EncryptOption {
var pretty bool
for _, option := range options {
switch option.Ident() {
case identPretty{}:
if err := option.Value(&pretty); err != nil {
panic(`jwe.WithJSON() requires bool value for WithPretty option`)
}
}
}
format := fmtJSON
if pretty {
format = fmtJSONPretty
}
return &encryptOption{option.New(identSerialization{}, format)}
}
+210
View File
@@ -0,0 +1,210 @@
package_name: jwe
output: jwe/options_gen.go
interfaces:
- name: GlobalOption
comment: |
GlobalOption describes options that changes global settings for this package
- name: GlobalDecryptOption
comment: |
GlobalDecryptOption describes options that changes global settings and for each call of the `jwe.Decrypt` function
methods:
- globalOption
- decryptOption
- name: CompactOption
comment: |
CompactOption describes options that can be passed to `jwe.Compact`
- name: DecryptOption
comment: |
DecryptOption describes options that can be passed to `jwe.Decrypt`
- name: EncryptOption
comment: |
EncryptOption describes options that can be passed to `jwe.Encrypt`
- name: EncryptDecryptOption
methods:
- encryptOption
- decryptOption
comment: |
EncryptDecryptOption describes options that can be passed to either `jwe.Encrypt` or `jwe.Decrypt`
- name: WithJSONSuboption
concrete_type: withJSONSuboption
comment: |
JSONSuboption describes suboptions that can be passed to `jwe.WithJSON()` option
- name: WithKeySetSuboption
comment: |
WithKeySetSuboption is a suboption passed to the WithKeySet() option
- name: ParseOption
methods:
- readFileOption
comment: |
ReadFileOption is a type of `Option` that can be passed to `jwe.Parse`
- name: ReadFileOption
comment: |
ReadFileOption is a type of `Option` that can be passed to `jwe.ReadFile`
options:
- ident: Key
skip_option: true
- ident: Pretty
skip_option: true
- ident: ProtectedHeaders
skip_option: true
- ident: PerRecipientHeaders
skip_option: true
- ident: KeyProvider
interface: DecryptOption
argument_type: KeyProvider
- ident: Context
interface: DecryptOption
argument_type: context.Context
comment: |
WithContext specifies the context.Context object to use when decrypting a JWE message.
If not provided, context.Background() will be used.
- ident: Serialization
option_name: WithCompact
interface: EncryptOption
constant_value: fmtCompact
comment: |
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
- ident: Compress
interface: EncryptOption
argument_type: jwa.CompressionAlgorithm
comment: |
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)
- ident: ContentEncryptionAlgorithm
interface: EncryptOption
option_name: WithContentEncryption
argument_type: jwa.ContentEncryptionAlgorithm
comment: |
WithContentEncryptionAlgorithm specifies the algorithm to encrypt the
JWE message content with. If not provided, `jwa.A256GCM` is used.
- ident: Message
interface: DecryptOption
argument_type: '*Message'
comment: |
WithMessage provides a message object to be populated by `jwe.Decrypt`
Using this option allows you to decrypt AND obtain the `jwe.Message`
in one go.
- ident: RequireKid
interface: WithKeySetSuboption
argument_type: bool
comment: |
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.
- ident: Pretty
interface: WithJSONSuboption
argument_type: bool
comment: |
WithPretty specifies whether the JSON output should be formatted and
indented
- ident: MergeProtectedHeaders
interface: EncryptOption
argument_type: bool
comment: |
WithMergeProtectedHeaders specify that when given multiple headers
as options to `jwe.Encrypt`, these headers should be merged instead
of overwritten
- ident: FS
interface: ReadFileOption
argument_type: fs.FS
comment: |
WithFS specifies the source `fs.FS` object to read the file from.
- ident: KeyUsed
interface: DecryptOption
argument_type: 'any'
comment: |
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
CEK.
`v` must be a pointer to an empty `any`. 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)
- ident: CEK
interface: DecryptOption
argument_type: '*[]byte'
comment: |
WithCEK allows users to specify a variable to store the CEK used in the
message upon successful decryption. The variable must be a pointer to
a byte slice, and it will only be populated if the decryption is successful.
This option is currently considered EXPERIMENTAL, and is subject to
future changes across minor/micro versions.
- ident: MaxPBES2Count
interface: GlobalOption
argument_type: int
comment: |
WithMaxPBES2Count specifies the maximum number of PBES2 iterations
to use when decrypting a message. If not specified, the default
value of 10,000 is used.
This option has a global effect.
- ident: MaxDecompressBufferSize
interface: GlobalDecryptOption
argument_type: int64
comment: |
WithMaxDecompressBufferSize specifies the maximum buffer size for used when
decompressing the payload of a JWE message. If a compressed JWE payload
exceeds this amount when decompressed, jwe.Decrypt will return an error.
The default value is 10MB.
This option can be used for `jwe.Settings()`, which changes the behavior
globally, or for `jwe.Decrypt()`, which changes the behavior for that
specific call.
- ident: CBCBufferSize
interface: GlobalOption
argument_type: int64
comment: |
WithCBCBufferSize specifies the maximum buffer size for internal
calculations, such as when AES-CBC is performed. The default value is 256MB.
If set to an invalid value, the default value is used.
In v2, this option was called MaxBufferSize.
This option has a global effect.
- ident: LegacyHeaderMerging
interface: EncryptOption
argument_type: bool
option_name: WithLegacyHeaderMerging
comment: |
WithLegacyHeaderMerging specifies whether to perform legacy header merging
when encrypting a JWE message in JSON serialization, when there is a single recipient.
This behavior is enabled by default for backwards compatibility.
When a JWE message is encrypted in JSON serialization, and there is only
one recipient, this library automatically serializes the message in
flattened JSON serialization format. In older versions of this library,
the protected headers and the per-recipient headers were merged together
before computing the AAD (Additional Authenticated Data), but the per-recipient
headers were kept as-is in the `header` field of the recipient object.
This behavior is not compliant with the JWE specification, which states that
the headers must be disjoint.
Passing this option with a value of `false` disables this legacy behavior,
and while the per-recipient headers and protected headers are still merged
for the purpose of computing AAD, the per-recipient headers are cleared
after merging, so that the resulting JWE message is compliant with the
specification.
This option has no effect when there are multiple recipients, or when
the serialization format is compact serialization. For multiple recipients
(i.e. full JSON serialization), the protected headers and per-recipient
headers are never merged, and it is the caller's responsibility to ensure
that the headers are disjoint. In compact serialization, there are no per-recipient
headers; in fact, the protected headers are the only headers that exist,
and therefore there is no possibility of header collision after merging
(note: while per-recipient headers do not make sense in compact serialization,
this library does not prevent you from setting them -- they are all just
merged into the protected headers).
In future versions, the new behavior will be the default. New users are
encouraged to set this option to `false` now to avoid future issues.
+392
View File
@@ -0,0 +1,392 @@
// Code generated by tools/cmd/genoptions/main.go. DO NOT EDIT.
package jwe
import (
"context"
"io/fs"
"github.com/lestrrat-go/jwx/v3/jwa"
"github.com/lestrrat-go/option/v2"
)
type Option = option.Interface
// CompactOption describes options that can be passed to `jwe.Compact`
type CompactOption interface {
Option
compactOption()
}
type compactOption struct {
Option
}
func (*compactOption) compactOption() {}
// DecryptOption describes options that can be passed to `jwe.Decrypt`
type DecryptOption interface {
Option
decryptOption()
}
type decryptOption struct {
Option
}
func (*decryptOption) decryptOption() {}
// EncryptDecryptOption describes options that can be passed to either `jwe.Encrypt` or `jwe.Decrypt`
type EncryptDecryptOption interface {
Option
encryptOption()
decryptOption()
}
type encryptDecryptOption struct {
Option
}
func (*encryptDecryptOption) encryptOption() {}
func (*encryptDecryptOption) decryptOption() {}
// EncryptOption describes options that can be passed to `jwe.Encrypt`
type EncryptOption interface {
Option
encryptOption()
}
type encryptOption struct {
Option
}
func (*encryptOption) encryptOption() {}
// GlobalDecryptOption describes options that changes global settings and for each call of the `jwe.Decrypt` function
type GlobalDecryptOption interface {
Option
globalOption()
decryptOption()
}
type globalDecryptOption struct {
Option
}
func (*globalDecryptOption) globalOption() {}
func (*globalDecryptOption) decryptOption() {}
// GlobalOption describes options that changes global settings for this package
type GlobalOption interface {
Option
globalOption()
}
type globalOption struct {
Option
}
func (*globalOption) globalOption() {}
// ReadFileOption is a type of `Option` that can be passed to `jwe.Parse`
type ParseOption interface {
Option
readFileOption()
}
type parseOption struct {
Option
}
func (*parseOption) readFileOption() {}
// ReadFileOption is a type of `Option` that can be passed to `jwe.ReadFile`
type ReadFileOption interface {
Option
readFileOption()
}
type readFileOption struct {
Option
}
func (*readFileOption) readFileOption() {}
// JSONSuboption describes suboptions that can be passed to `jwe.WithJSON()` option
type WithJSONSuboption interface {
Option
withJSONSuboption()
}
type withJSONSuboption struct {
Option
}
func (*withJSONSuboption) withJSONSuboption() {}
// WithKeySetSuboption is a suboption passed to the WithKeySet() option
type WithKeySetSuboption interface {
Option
withKeySetSuboption()
}
type withKeySetSuboption struct {
Option
}
func (*withKeySetSuboption) withKeySetSuboption() {}
type identCBCBufferSize struct{}
type identCEK struct{}
type identCompress struct{}
type identContentEncryptionAlgorithm struct{}
type identContext struct{}
type identFS struct{}
type identKey struct{}
type identKeyProvider struct{}
type identKeyUsed struct{}
type identLegacyHeaderMerging struct{}
type identMaxDecompressBufferSize struct{}
type identMaxPBES2Count struct{}
type identMergeProtectedHeaders struct{}
type identMessage struct{}
type identPerRecipientHeaders struct{}
type identPretty struct{}
type identProtectedHeaders struct{}
type identRequireKid struct{}
type identSerialization struct{}
func (identCBCBufferSize) String() string {
return "WithCBCBufferSize"
}
func (identCEK) String() string {
return "WithCEK"
}
func (identCompress) String() string {
return "WithCompress"
}
func (identContentEncryptionAlgorithm) String() string {
return "WithContentEncryption"
}
func (identContext) String() string {
return "WithContext"
}
func (identFS) String() string {
return "WithFS"
}
func (identKey) String() string {
return "WithKey"
}
func (identKeyProvider) String() string {
return "WithKeyProvider"
}
func (identKeyUsed) String() string {
return "WithKeyUsed"
}
func (identLegacyHeaderMerging) String() string {
return "WithLegacyHeaderMerging"
}
func (identMaxDecompressBufferSize) String() string {
return "WithMaxDecompressBufferSize"
}
func (identMaxPBES2Count) String() string {
return "WithMaxPBES2Count"
}
func (identMergeProtectedHeaders) String() string {
return "WithMergeProtectedHeaders"
}
func (identMessage) String() string {
return "WithMessage"
}
func (identPerRecipientHeaders) String() string {
return "WithPerRecipientHeaders"
}
func (identPretty) String() string {
return "WithPretty"
}
func (identProtectedHeaders) String() string {
return "WithProtectedHeaders"
}
func (identRequireKid) String() string {
return "WithRequireKid"
}
func (identSerialization) String() string {
return "WithSerialization"
}
// WithCBCBufferSize specifies the maximum buffer size for internal
// calculations, such as when AES-CBC is performed. The default value is 256MB.
// If set to an invalid value, the default value is used.
// In v2, this option was called MaxBufferSize.
//
// This option has a global effect.
func WithCBCBufferSize(v int64) GlobalOption {
return &globalOption{option.New(identCBCBufferSize{}, v)}
}
// WithCEK allows users to specify a variable to store the CEK used in the
// message upon successful decryption. The variable must be a pointer to
// a byte slice, and it will only be populated if the decryption is successful.
//
// This option is currently considered EXPERIMENTAL, and is subject to
// future changes across minor/micro versions.
func WithCEK(v *[]byte) DecryptOption {
return &decryptOption{option.New(identCEK{}, v)}
}
// 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 WithCompress(v jwa.CompressionAlgorithm) EncryptOption {
return &encryptOption{option.New(identCompress{}, v)}
}
// WithContentEncryptionAlgorithm specifies the algorithm to encrypt the
// JWE message content with. If not provided, `jwa.A256GCM` is used.
func WithContentEncryption(v jwa.ContentEncryptionAlgorithm) EncryptOption {
return &encryptOption{option.New(identContentEncryptionAlgorithm{}, v)}
}
// WithContext specifies the context.Context object to use when decrypting a JWE message.
// If not provided, context.Background() will be used.
func WithContext(v context.Context) DecryptOption {
return &decryptOption{option.New(identContext{}, v)}
}
// WithFS specifies the source `fs.FS` object to read the file from.
func WithFS(v fs.FS) ReadFileOption {
return &readFileOption{option.New(identFS{}, v)}
}
func WithKeyProvider(v KeyProvider) DecryptOption {
return &decryptOption{option.New(identKeyProvider{}, v)}
}
// 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
// CEK.
//
// `v` must be a pointer to an empty `any`. 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 WithKeyUsed(v any) DecryptOption {
return &decryptOption{option.New(identKeyUsed{}, v)}
}
// WithLegacyHeaderMerging specifies whether to perform legacy header merging
// when encrypting a JWE message in JSON serialization, when there is a single recipient.
// This behavior is enabled by default for backwards compatibility.
//
// When a JWE message is encrypted in JSON serialization, and there is only
// one recipient, this library automatically serializes the message in
// flattened JSON serialization format. In older versions of this library,
// the protected headers and the per-recipient headers were merged together
// before computing the AAD (Additional Authenticated Data), but the per-recipient
// headers were kept as-is in the `header` field of the recipient object.
//
// This behavior is not compliant with the JWE specification, which states that
// the headers must be disjoint.
//
// Passing this option with a value of `false` disables this legacy behavior,
// and while the per-recipient headers and protected headers are still merged
// for the purpose of computing AAD, the per-recipient headers are cleared
// after merging, so that the resulting JWE message is compliant with the
// specification.
//
// This option has no effect when there are multiple recipients, or when
// the serialization format is compact serialization. For multiple recipients
// (i.e. full JSON serialization), the protected headers and per-recipient
// headers are never merged, and it is the caller's responsibility to ensure
// that the headers are disjoint. In compact serialization, there are no per-recipient
// headers; in fact, the protected headers are the only headers that exist,
// and therefore there is no possibility of header collision after merging
// (note: while per-recipient headers do not make sense in compact serialization,
// this library does not prevent you from setting them -- they are all just
// merged into the protected headers).
//
// In future versions, the new behavior will be the default. New users are
// encouraged to set this option to `false` now to avoid future issues.
func WithLegacyHeaderMerging(v bool) EncryptOption {
return &encryptOption{option.New(identLegacyHeaderMerging{}, v)}
}
// WithMaxDecompressBufferSize specifies the maximum buffer size for used when
// decompressing the payload of a JWE message. If a compressed JWE payload
// exceeds this amount when decompressed, jwe.Decrypt will return an error.
// The default value is 10MB.
//
// This option can be used for `jwe.Settings()`, which changes the behavior
// globally, or for `jwe.Decrypt()`, which changes the behavior for that
// specific call.
func WithMaxDecompressBufferSize(v int64) GlobalDecryptOption {
return &globalDecryptOption{option.New(identMaxDecompressBufferSize{}, v)}
}
// WithMaxPBES2Count specifies the maximum number of PBES2 iterations
// to use when decrypting a message. If not specified, the default
// value of 10,000 is used.
//
// This option has a global effect.
func WithMaxPBES2Count(v int) GlobalOption {
return &globalOption{option.New(identMaxPBES2Count{}, v)}
}
// WithMergeProtectedHeaders specify that when given multiple headers
// as options to `jwe.Encrypt`, these headers should be merged instead
// of overwritten
func WithMergeProtectedHeaders(v bool) EncryptOption {
return &encryptOption{option.New(identMergeProtectedHeaders{}, v)}
}
// WithMessage provides a message object to be populated by `jwe.Decrypt`
// Using this option allows you to decrypt AND obtain the `jwe.Message`
// in one go.
func WithMessage(v *Message) DecryptOption {
return &decryptOption{option.New(identMessage{}, v)}
}
// WithPretty specifies whether the JSON output should be formatted and
// indented
func WithPretty(v bool) WithJSONSuboption {
return &withJSONSuboption{option.New(identPretty{}, v)}
}
// 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.
func WithRequireKid(v bool) WithKeySetSuboption {
return &withKeySetSuboption{option.New(identRequireKid{}, v)}
}
// 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 WithCompact() EncryptOption {
return &encryptOption{option.New(identSerialization{}, fmtCompact)}
}