Files
Курнат Андрей 2315f25754 Initial QSfera import
2026-06-07 10:20:04 +03:00

1119 lines
35 KiB
Go

//go:generate ../tools/cmd/genjwe.sh
// Package jwe implements JWE as described in https://tools.ietf.org/html/rfc7516
package jwe
// #region imports
import (
"bytes"
"context"
"crypto/ecdsa"
"errors"
"fmt"
"io"
"sync"
"github.com/lestrrat-go/blackmagic"
"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/jwk"
"github.com/lestrrat-go/jwx/v3/jwa"
"github.com/lestrrat-go/jwx/v3/jwe/internal/aescbc"
"github.com/lestrrat-go/jwx/v3/jwe/internal/content_crypt"
"github.com/lestrrat-go/jwx/v3/jwe/internal/keygen"
)
// #region globals
var muSettings sync.RWMutex
var maxPBES2Count = 10000
var maxDecompressBufferSize int64 = 10 * 1024 * 1024 // 10MB
func Settings(options ...GlobalOption) {
muSettings.Lock()
defer muSettings.Unlock()
for _, option := range options {
switch option.Ident() {
case identMaxPBES2Count{}:
if err := option.Value(&maxPBES2Count); err != nil {
panic(fmt.Sprintf("jwe.Settings: value for option WithMaxPBES2Count must be an int: %s", err))
}
case identMaxDecompressBufferSize{}:
if err := option.Value(&maxDecompressBufferSize); err != nil {
panic(fmt.Sprintf("jwe.Settings: value for option WithMaxDecompressBufferSize must be an int64: %s", err))
}
case identCBCBufferSize{}:
var v int64
if err := option.Value(&v); err != nil {
panic(fmt.Sprintf("jwe.Settings: value for option WithCBCBufferSize must be an int64: %s", err))
}
aescbc.SetMaxBufferSize(v)
}
}
}
const (
fmtInvalid = iota
fmtCompact
fmtJSON
fmtJSONPretty
fmtMax
)
var _ = fmtInvalid
var _ = fmtMax
var registry = json.NewRegistry()
type recipientBuilder struct {
alg jwa.KeyEncryptionAlgorithm
key any
headers Headers
}
func (b *recipientBuilder) Build(r Recipient, cek []byte, calg jwa.ContentEncryptionAlgorithm, _ *content_crypt.Generic) ([]byte, error) {
// we need the raw key for later use
rawKey := b.key
var keyID string
if ke, ok := b.key.(KeyEncrypter); ok {
if kider, ok := ke.(KeyIDer); ok {
if v, ok := kider.KeyID(); ok {
keyID = v
}
}
} else if jwkKey, ok := b.key.(jwk.Key); ok {
// Meanwhile, grab the kid as well
if v, ok := jwkKey.KeyID(); ok {
keyID = v
}
var raw any
if err := jwk.Export(jwkKey, &raw); err != nil {
return nil, fmt.Errorf(`jwe.Encrypt: recipientBuilder: failed to retrieve raw key out of %T: %w`, b.key, err)
}
rawKey = raw
}
// Extract ECDH-ES specific parameters if needed.
var apu, apv []byte
hdr := b.headers
if hdr == nil {
hdr = NewHeaders()
}
if val, ok := hdr.AgreementPartyUInfo(); ok {
apu = val
}
if val, ok := hdr.AgreementPartyVInfo(); ok {
apv = val
}
// Create the encrypter using the new jwebb pattern
enc, err := newEncrypter(b.alg, calg, b.key, rawKey, apu, apv)
if err != nil {
return nil, fmt.Errorf(`jwe.Encrypt: recipientBuilder: failed to create encrypter: %w`, err)
}
_ = r.SetHeaders(hdr)
// Populate headers with stuff that we automatically set
if err := hdr.Set(AlgorithmKey, b.alg); err != nil {
return nil, fmt.Errorf(`failed to set header: %w`, err)
}
if keyID != "" {
if err := hdr.Set(KeyIDKey, keyID); err != nil {
return nil, fmt.Errorf(`failed to set header: %w`, err)
}
}
// Handle the encrypted key
var rawCEK []byte
enckey, err := enc.EncryptKey(cek)
if err != nil {
return nil, fmt.Errorf(`failed to encrypt key: %w`, err)
}
if b.alg == jwa.ECDH_ES() || b.alg == jwa.DIRECT() {
rawCEK = enckey.Bytes()
} else {
if err := r.SetEncryptedKey(enckey.Bytes()); err != nil {
return nil, fmt.Errorf(`failed to set encrypted key: %w`, err)
}
}
// finally, anything specific should go here
if hp, ok := enckey.(populater); ok {
if err := hp.Populate(hdr); err != nil {
return nil, fmt.Errorf(`failed to populate: %w`, err)
}
}
return rawCEK, nil
}
// Encrypt generates a JWE message for the given payload and returns
// it in serialized form, which can be in either compact or
// JSON format. Default is compact. When JSON format is specified and
// there is only one recipient, the resulting serialization is
// automatically converted to flattened JSON serialization format.
//
// You must pass at least one key to `jwe.Encrypt()` by using `jwe.WithKey()`
// option.
//
// jwe.Encrypt(payload, jwe.WithKey(alg, key))
// jwe.Encrypt(payload, jws.WithJSON(), jws.WithKey(alg1, key1), jws.WithKey(alg2, key2))
//
// Note that in the second example the `jws.WithJSON()` option is
// specified as well. This is because the compact serialization
// format does not support multiple recipients, and users must
// specifically ask for the JSON serialization format.
//
// Read the documentation for `jwe.WithKey()` to learn more about the
// possible values that can be used for `alg` and `key`.
//
// Look for options that return `jwe.EncryptOption` or `jws.EncryptDecryptOption`
// for a complete list of options that can be passed to this function.
//
// As of v3.0.12, users can specify `jwe.WithLegacyHeaderMerging()` to
// disable header merging behavior that was the default prior to v3.0.12.
// Read the documentation for `jwe.WithLegacyHeaderMerging()` for more information.
func Encrypt(payload []byte, options ...EncryptOption) ([]byte, error) {
ec := encryptContextPool.Get()
defer encryptContextPool.Put(ec)
if err := ec.ProcessOptions(options); err != nil {
return nil, encryptError{fmt.Errorf(`jwe.Encrypt: failed to process options: %w`, err)}
}
ret, err := ec.EncryptMessage(payload, nil)
if err != nil {
return nil, encryptError{fmt.Errorf(`jwe.Encrypt: %w`, err)}
}
return ret, nil
}
// EncryptStatic is exactly like Encrypt, except it accepts a static
// content encryption key (CEK). It is separated out from the main
// Encrypt function such that the latter does not accidentally use a static
// CEK.
//
// DO NOT attempt to use this function unless you completely understand the
// security implications to using static CEKs. You have been warned.
//
// This function is currently considered EXPERIMENTAL, and is subject to
// future changes across minor/micro versions.
func EncryptStatic(payload, cek []byte, options ...EncryptOption) ([]byte, error) {
if len(cek) <= 0 {
return nil, encryptError{fmt.Errorf(`jwe.EncryptStatic: empty CEK`)}
}
ec := encryptContextPool.Get()
defer encryptContextPool.Put(ec)
if err := ec.ProcessOptions(options); err != nil {
return nil, encryptError{fmt.Errorf(`jwe.EncryptStatic: failed to process options: %w`, err)}
}
ret, err := ec.EncryptMessage(payload, cek)
if err != nil {
return nil, encryptError{fmt.Errorf(`jwe.EncryptStatic: %w`, err)}
}
return ret, nil
}
// decryptContext holds the state during JWE decryption, similar to JWS verifyContext
type decryptContext struct {
keyProviders []KeyProvider
keyUsed any
cek *[]byte
dst *Message
maxDecompressBufferSize int64
//nolint:containedctx
ctx context.Context
}
var decryptContextPool = pool.New(allocDecryptContext, freeDecryptContext)
func allocDecryptContext() *decryptContext {
return &decryptContext{
ctx: context.Background(),
}
}
func freeDecryptContext(dc *decryptContext) *decryptContext {
dc.keyProviders = dc.keyProviders[:0]
dc.keyUsed = nil
dc.cek = nil
dc.dst = nil
dc.maxDecompressBufferSize = 0
dc.ctx = context.Background()
return dc
}
func (dc *decryptContext) ProcessOptions(options []DecryptOption) error {
// Set default max decompress buffer size
muSettings.RLock()
dc.maxDecompressBufferSize = maxDecompressBufferSize
muSettings.RUnlock()
for _, option := range options {
switch option.Ident() {
case identMessage{}:
if err := option.Value(&dc.dst); err != nil {
return fmt.Errorf("jwe.decrypt: WithMessage must be a *jwe.Message: %w", err)
}
case identKeyProvider{}:
var kp KeyProvider
if err := option.Value(&kp); err != nil {
return fmt.Errorf("jwe.decrypt: WithKeyProvider must be a KeyProvider: %w", err)
}
dc.keyProviders = append(dc.keyProviders, kp)
case identKeyUsed{}:
if err := option.Value(&dc.keyUsed); err != nil {
return fmt.Errorf("jwe.decrypt: WithKeyUsed must be an any: %w", err)
}
case identKey{}:
var pair *withKey
if err := option.Value(&pair); err != nil {
return fmt.Errorf("jwe.decrypt: WithKey must be a *withKey: %w", err)
}
alg, ok := pair.alg.(jwa.KeyEncryptionAlgorithm)
if !ok {
return fmt.Errorf("jwe.decrypt: WithKey() option must be specified using jwa.KeyEncryptionAlgorithm (got %T)", pair.alg)
}
dc.keyProviders = append(dc.keyProviders, &staticKeyProvider{alg: alg, key: pair.key})
case identCEK{}:
if err := option.Value(&dc.cek); err != nil {
return fmt.Errorf("jwe.decrypt: WithCEK must be a *[]byte: %w", err)
}
case identMaxDecompressBufferSize{}:
if err := option.Value(&dc.maxDecompressBufferSize); err != nil {
return fmt.Errorf("jwe.decrypt: WithMaxDecompressBufferSize must be int64: %w", err)
}
case identContext{}:
if err := option.Value(&dc.ctx); err != nil {
return fmt.Errorf("jwe.decrypt: WithContext must be a context.Context: %w", err)
}
}
}
if len(dc.keyProviders) < 1 {
return fmt.Errorf(`jwe.Decrypt: no key providers have been provided (see jwe.WithKey(), jwe.WithKeySet(), and jwe.WithKeyProvider()`)
}
return nil
}
func (dc *decryptContext) DecryptMessage(buf []byte) ([]byte, error) {
msg, err := parseJSONOrCompact(buf, true)
if err != nil {
return nil, fmt.Errorf(`failed to parse buffer for Decrypt: %w`, err)
}
// Process things that are common to the message
h, err := msg.protectedHeaders.Clone()
if err != nil {
return nil, fmt.Errorf(`failed to copy protected headers: %w`, err)
}
h, err = h.Merge(msg.unprotectedHeaders)
if err != nil {
return nil, fmt.Errorf(`failed to merge headers for message decryption: %w`, err)
}
var aad []byte
if aadContainer := msg.authenticatedData; aadContainer != nil {
aad = base64.Encode(aadContainer)
}
var computedAad []byte
if len(msg.rawProtectedHeaders) > 0 {
computedAad = msg.rawProtectedHeaders
} else {
// this is probably not required once msg.Decrypt is deprecated
var err error
computedAad, err = msg.protectedHeaders.Encode()
if err != nil {
return nil, fmt.Errorf(`failed to encode protected headers: %w`, err)
}
}
// for each recipient, attempt to match the key providers
// if we have no recipients, pretend like we only have one
recipients := msg.recipients
if len(recipients) == 0 {
r := NewRecipient()
if err := r.SetHeaders(msg.protectedHeaders); err != nil {
return nil, fmt.Errorf(`failed to set headers to recipient: %w`, err)
}
recipients = append(recipients, r)
}
errs := make([]error, 0, len(recipients))
for _, recipient := range recipients {
decrypted, err := dc.tryRecipient(msg, recipient, h, aad, computedAad)
if err != nil {
errs = append(errs, recipientError{err})
continue
}
if dc.dst != nil {
*dc.dst = *msg
dc.dst.rawProtectedHeaders = nil
dc.dst.storeProtectedHeaders = false
}
return decrypted, nil
}
return nil, fmt.Errorf(`failed to decrypt any of the recipients: %w`, errors.Join(errs...))
}
func (dc *decryptContext) tryRecipient(msg *Message, recipient Recipient, protectedHeaders Headers, aad, computedAad []byte) ([]byte, error) {
var tried int
var lastError error
for i, kp := range dc.keyProviders {
var sink algKeySink
if err := kp.FetchKeys(dc.ctx, &sink, recipient, msg); err != nil {
return nil, fmt.Errorf(`key provider %d failed: %w`, i, err)
}
for _, pair := range sink.list {
tried++
// alg is converted here because pair.alg is of type jwa.KeyAlgorithm.
// this may seem ugly, but we're trying to avoid declaring separate
// structs for `alg jwa.KeyEncryptionAlgorithm` and `alg jwa.SignatureAlgorithm`
//nolint:forcetypeassert
alg := pair.alg.(jwa.KeyEncryptionAlgorithm)
key := pair.key
decrypted, err := dc.decryptContent(msg, alg, key, recipient, protectedHeaders, aad, computedAad)
if err != nil {
lastError = err
continue
}
if dc.keyUsed != nil {
if err := blackmagic.AssignIfCompatible(dc.keyUsed, key); err != nil {
return nil, fmt.Errorf(`failed to assign used key (%T) to %T: %w`, key, dc.keyUsed, err)
}
}
return decrypted, nil
}
}
return nil, fmt.Errorf(`jwe.Decrypt: tried %d keys, but failed to match any of the keys with recipient (last error = %s)`, tried, lastError)
}
func (dc *decryptContext) decryptContent(msg *Message, alg jwa.KeyEncryptionAlgorithm, key any, recipient Recipient, protectedHeaders Headers, aad, computedAad []byte) ([]byte, error) {
if jwkKey, ok := key.(jwk.Key); ok {
var raw any
if err := jwk.Export(jwkKey, &raw); err != nil {
return nil, fmt.Errorf(`failed to retrieve raw key from %T: %w`, key, err)
}
key = raw
}
ce, ok := msg.protectedHeaders.ContentEncryption()
if !ok {
return nil, fmt.Errorf(`jwe.Decrypt: failed to retrieve content encryption algorithm from protected headers`)
}
dec := newDecrypter(alg, ce, key).
AuthenticatedData(aad).
ComputedAuthenticatedData(computedAad).
InitializationVector(msg.initializationVector).
Tag(msg.tag).
CEK(dc.cek)
// The "alg" header can be in either protected/unprotected headers.
// prefer per-recipient headers (as it might be the case that the algorithm differs
// by each recipient), then look at protected headers.
var algMatched bool
for _, hdr := range []Headers{recipient.Headers(), protectedHeaders} {
v, ok := hdr.Algorithm()
if !ok {
continue
}
if v == alg {
algMatched = true
break
}
// if we found something but didn't match, it's a failure
return nil, fmt.Errorf(`jwe.Decrypt: key (%q) and recipient (%q) algorithms do not match`, alg, v)
}
if !algMatched {
return nil, fmt.Errorf(`jwe.Decrypt: failed to find "alg" header in either protected or per-recipient headers`)
}
h2, err := protectedHeaders.Clone()
if err != nil {
return nil, fmt.Errorf(`jwe.Decrypt: failed to copy headers (1): %w`, err)
}
h2, err = h2.Merge(recipient.Headers())
if err != nil {
return nil, fmt.Errorf(`failed to copy headers (2): %w`, err)
}
switch alg {
case jwa.ECDH_ES(), jwa.ECDH_ES_A128KW(), jwa.ECDH_ES_A192KW(), jwa.ECDH_ES_A256KW():
var epk any
if err := h2.Get(EphemeralPublicKeyKey, &epk); err != nil {
return nil, fmt.Errorf(`failed to get 'epk' field: %w`, err)
}
switch epk := epk.(type) {
case jwk.ECDSAPublicKey:
var pubkey ecdsa.PublicKey
if err := jwk.Export(epk, &pubkey); err != nil {
return nil, fmt.Errorf(`failed to get public key: %w`, err)
}
dec.PublicKey(&pubkey)
case jwk.OKPPublicKey:
var pubkey any
if err := jwk.Export(epk, &pubkey); err != nil {
return nil, fmt.Errorf(`failed to get public key: %w`, err)
}
dec.PublicKey(pubkey)
default:
return nil, fmt.Errorf("unexpected 'epk' type %T for alg %s", epk, alg)
}
if apu, ok := h2.AgreementPartyUInfo(); ok && len(apu) > 0 {
dec.AgreementPartyUInfo(apu)
}
if apv, ok := h2.AgreementPartyVInfo(); ok && len(apv) > 0 {
dec.AgreementPartyVInfo(apv)
}
case jwa.A128GCMKW(), jwa.A192GCMKW(), jwa.A256GCMKW():
var ivB64 string
if err := h2.Get(InitializationVectorKey, &ivB64); err == nil {
iv, err := base64.DecodeString(ivB64)
if err != nil {
return nil, fmt.Errorf(`failed to b64-decode 'iv': %w`, err)
}
dec.KeyInitializationVector(iv)
}
var tagB64 string
if err := h2.Get(TagKey, &tagB64); err == nil {
tag, err := base64.DecodeString(tagB64)
if err != nil {
return nil, fmt.Errorf(`failed to b64-decode 'tag': %w`, err)
}
dec.KeyTag(tag)
}
case jwa.PBES2_HS256_A128KW(), jwa.PBES2_HS384_A192KW(), jwa.PBES2_HS512_A256KW():
var saltB64 string
if err := h2.Get(SaltKey, &saltB64); err != nil {
return nil, fmt.Errorf(`failed to get %q field`, SaltKey)
}
// check if WithUseNumber is effective, because it will change the
// type of the underlying value (#1140)
var countFlt float64
if json.UseNumber() {
var count json.Number
if err := h2.Get(CountKey, &count); err != nil {
return nil, fmt.Errorf(`failed to get %q field`, CountKey)
}
v, err := count.Float64()
if err != nil {
return nil, fmt.Errorf("failed to convert 'p2c' to float64: %w", err)
}
countFlt = v
} else {
var count float64
if err := h2.Get(CountKey, &count); err != nil {
return nil, fmt.Errorf(`failed to get %q field`, CountKey)
}
countFlt = count
}
muSettings.RLock()
maxCount := maxPBES2Count
muSettings.RUnlock()
if countFlt > float64(maxCount) {
return nil, fmt.Errorf("invalid 'p2c' value")
}
salt, err := base64.DecodeString(saltB64)
if err != nil {
return nil, fmt.Errorf(`failed to b64-decode 'salt': %w`, err)
}
dec.KeySalt(salt)
dec.KeyCount(int(countFlt))
}
plaintext, err := dec.Decrypt(recipient, msg.cipherText, msg)
if err != nil {
return nil, fmt.Errorf(`jwe.Decrypt: decryption failed: %w`, err)
}
if v, ok := h2.Compression(); ok && v == jwa.Deflate() {
buf, err := uncompress(plaintext, dc.maxDecompressBufferSize)
if err != nil {
return nil, fmt.Errorf(`jwe.Derypt: failed to uncompress payload: %w`, err)
}
plaintext = buf
}
if plaintext == nil {
return nil, fmt.Errorf(`failed to find matching recipient`)
}
return plaintext, nil
}
// encryptContext holds the state during JWE encryption, similar to JWS signContext
type encryptContext struct {
calg jwa.ContentEncryptionAlgorithm
compression jwa.CompressionAlgorithm
format int
builders []*recipientBuilder
protected Headers
legacyHeaderMerging bool
}
var encryptContextPool = pool.New(allocEncryptContext, freeEncryptContext)
func allocEncryptContext() *encryptContext {
return &encryptContext{
calg: jwa.A256GCM(),
compression: jwa.NoCompress(),
format: fmtCompact,
}
}
func freeEncryptContext(ec *encryptContext) *encryptContext {
ec.calg = jwa.A256GCM()
ec.compression = jwa.NoCompress()
ec.format = fmtCompact
ec.builders = ec.builders[:0]
ec.protected = nil
return ec
}
func (ec *encryptContext) ProcessOptions(options []EncryptOption) error {
ec.legacyHeaderMerging = true
var mergeProtected bool
var useRawCEK bool
for _, option := range options {
switch option.Ident() {
case identKey{}:
var wk *withKey
if err := option.Value(&wk); err != nil {
return fmt.Errorf("jwe.encrypt: WithKey must be a *withKey: %w", err)
}
v, ok := wk.alg.(jwa.KeyEncryptionAlgorithm)
if !ok {
return fmt.Errorf("jwe.encrypt: WithKey() option must be specified using jwa.KeyEncryptionAlgorithm (got %T)", wk.alg)
}
if v == jwa.DIRECT() || v == jwa.ECDH_ES() {
useRawCEK = true
}
ec.builders = append(ec.builders, &recipientBuilder{
alg: v,
key: wk.key,
headers: wk.headers,
})
case identContentEncryptionAlgorithm{}:
var c jwa.ContentEncryptionAlgorithm
if err := option.Value(&c); err != nil {
return err
}
ec.calg = c
case identCompress{}:
var comp jwa.CompressionAlgorithm
if err := option.Value(&comp); err != nil {
return err
}
ec.compression = comp
case identMergeProtectedHeaders{}:
var mp bool
if err := option.Value(&mp); err != nil {
return err
}
mergeProtected = mp
case identProtectedHeaders{}:
var hdrs Headers
if err := option.Value(&hdrs); err != nil {
return err
}
if !mergeProtected || ec.protected == nil {
ec.protected = hdrs
} else {
merged, err := ec.protected.Merge(hdrs)
if err != nil {
return fmt.Errorf(`failed to merge headers: %w`, err)
}
ec.protected = merged
}
case identSerialization{}:
var fmtOpt int
if err := option.Value(&fmtOpt); err != nil {
return err
}
ec.format = fmtOpt
case identLegacyHeaderMerging{}:
var v bool
if err := option.Value(&v); err != nil {
return err
}
ec.legacyHeaderMerging = v
}
}
// We need to have at least one builder
switch l := len(ec.builders); {
case l == 0:
return fmt.Errorf(`missing key encryption builders: use jwe.WithKey() to specify one`)
case l > 1:
if ec.format == fmtCompact {
return fmt.Errorf(`cannot use compact serialization when multiple recipients exist (check the number of WithKey() argument, or use WithJSON())`)
}
}
if useRawCEK {
if len(ec.builders) != 1 {
return fmt.Errorf(`multiple recipients for ECDH-ES/DIRECT mode supported`)
}
}
return nil
}
var msgPool = pool.New(allocMessage, freeMessage)
func allocMessage() *Message {
return &Message{
recipients: make([]Recipient, 0, 1),
}
}
func freeMessage(msg *Message) *Message {
msg.cipherText = nil
msg.initializationVector = nil
if hdr := msg.protectedHeaders; hdr != nil {
headerPool.Put(hdr)
}
msg.protectedHeaders = nil
msg.unprotectedHeaders = nil
msg.recipients = nil // reuse should be done elsewhere
msg.authenticatedData = nil
msg.tag = nil
msg.rawProtectedHeaders = nil
msg.storeProtectedHeaders = false
return msg
}
var headerPool = pool.New(NewHeaders, freeHeaders)
func freeHeaders(h Headers) Headers {
if c, ok := h.(interface{ clear() }); ok {
c.clear()
}
return h
}
var recipientPool = pool.New(NewRecipient, freeRecipient)
func freeRecipient(r Recipient) Recipient {
if h := r.Headers(); h != nil {
if c, ok := h.(interface{ clear() }); ok {
c.clear()
}
}
if sr, ok := r.(*stdRecipient); ok {
sr.encryptedKey = nil
}
return r
}
var recipientSlicePool = pool.NewSlicePool(allocRecipientSlice, freeRecipientSlice)
func allocRecipientSlice() []Recipient {
return make([]Recipient, 0, 1)
}
func freeRecipientSlice(rs []Recipient) []Recipient {
for _, r := range rs {
recipientPool.Put(r)
}
return rs[:0]
}
func (ec *encryptContext) EncryptMessage(payload []byte, cek []byte) ([]byte, error) {
// Get protected headers from pool and copy contents from context
protected := headerPool.Get()
if userSupplied := ec.protected; userSupplied != nil {
ec.protected = nil // Clear from context
if err := userSupplied.Copy(protected); err != nil {
return nil, fmt.Errorf(`failed to copy protected headers: %w`, err)
}
}
// There is exactly one content encrypter.
contentcrypt, err := content_crypt.NewGeneric(ec.calg)
if err != nil {
return nil, fmt.Errorf(`failed to create AES encrypter: %w`, err)
}
// Generate CEK if not provided
if len(cek) <= 0 {
bk, err := keygen.Random(contentcrypt.KeySize())
if err != nil {
return nil, fmt.Errorf(`failed to generate key: %w`, err)
}
cek = bk.Bytes()
}
var useRawCEK bool
for _, builder := range ec.builders {
if builder.alg == jwa.DIRECT() || builder.alg == jwa.ECDH_ES() {
useRawCEK = true
break
}
}
lbuilders := len(ec.builders)
recipients := recipientSlicePool.GetCapacity(lbuilders)
defer recipientSlicePool.Put(recipients)
for i, builder := range ec.builders {
r := recipientPool.Get()
defer recipientPool.Put(r)
// some builders require hint from the contentcrypt object
rawCEK, err := builder.Build(r, cek, ec.calg, contentcrypt)
if err != nil {
return nil, fmt.Errorf(`failed to create recipient #%d: %w`, i, err)
}
recipients = append(recipients, r)
// Kinda feels weird, but if useRawCEK == true, we asserted earlier
// that len(builders) == 1, so this is OK
if useRawCEK {
cek = rawCEK
}
}
if err := protected.Set(ContentEncryptionKey, ec.calg); err != nil {
return nil, fmt.Errorf(`failed to set "enc" in protected header: %w`, err)
}
if ec.compression != jwa.NoCompress() {
payload, err = compress(payload)
if err != nil {
return nil, fmt.Errorf(`failed to compress payload before encryption: %w`, err)
}
if err := protected.Set(CompressionKey, ec.compression); err != nil {
return nil, fmt.Errorf(`failed to set "zip" in protected header: %w`, err)
}
}
// fmtCompact does not have per-recipient headers, nor a "header" field.
// In this mode, we're going to have to merge everything to the protected
// header.
if ec.format == fmtCompact {
// We have already established that the number of builders is 1 in
// ec.ProcessOptions(). But we're going to be pedantic
if lbuilders != 1 {
return nil, fmt.Errorf(`internal error: expected exactly one recipient builder (got %d)`, lbuilders)
}
// when we're using compact format, we can safely merge per-recipient
// headers into the protected header, if any
h, err := protected.Merge(recipients[0].Headers())
if err != nil {
return nil, fmt.Errorf(`failed to merge protected headers for compact serialization: %w`, err)
}
protected = h
// per-recipient headers, if any, will be ignored in compact format
} else {
// If it got here, it's JSON (could be pretty mode, too).
if lbuilders == 1 {
// If it got here, then we're doing flattened JSON serialization.
// In this mode, we should merge per-recipient headers into the protected header,
// but we also need to make sure that the "header" field is reset so that
// it does not contain the same fields as the protected header.
//
// However, old behavior was to merge per-recipient headers into the
// protected header when there was only one recipient, AND leave the
// original "header" field as is, so we need to support that for backwards compatibility.
//
// The legacy merging only takes effect when there is exactly one recipient.
//
// This behavior can be disabled by passing jwe.WithLegacyHeaderMerging(false)
// If the user has explicitly asked for merging, do it
h, err := protected.Merge(recipients[0].Headers())
if err != nil {
return nil, fmt.Errorf(`failed to merge protected headers for flattenend JSON format: %w`, err)
}
protected = h
if !ec.legacyHeaderMerging {
// Clear per-recipient headers, since they have been merged.
// But we only do it when legacy merging is disabled.
// Note: we should probably introduce a Reset() method in v4
if err := recipients[0].SetHeaders(NewHeaders()); err != nil {
return nil, fmt.Errorf(`failed to clear per-recipient headers after merging: %w`, err)
}
}
}
}
aad, err := protected.Encode()
if err != nil {
return nil, fmt.Errorf(`failed to base64 encode protected headers: %w`, err)
}
iv, ciphertext, tag, err := contentcrypt.Encrypt(cek, payload, aad)
if err != nil {
return nil, fmt.Errorf(`failed to encrypt payload: %w`, err)
}
msg := msgPool.Get()
defer msgPool.Put(msg)
if err := msg.Set(CipherTextKey, ciphertext); err != nil {
return nil, fmt.Errorf(`failed to set %s: %w`, CipherTextKey, err)
}
if err := msg.Set(InitializationVectorKey, iv); err != nil {
return nil, fmt.Errorf(`failed to set %s: %w`, InitializationVectorKey, err)
}
if err := msg.Set(ProtectedHeadersKey, protected); err != nil {
return nil, fmt.Errorf(`failed to set %s: %w`, ProtectedHeadersKey, err)
}
if err := msg.Set(RecipientsKey, recipients); err != nil {
return nil, fmt.Errorf(`failed to set %s: %w`, RecipientsKey, err)
}
if err := msg.Set(TagKey, tag); err != nil {
return nil, fmt.Errorf(`failed to set %s: %w`, TagKey, err)
}
switch ec.format {
case fmtCompact:
return Compact(msg)
case fmtJSON:
return json.Marshal(msg)
case fmtJSONPretty:
return json.MarshalIndent(msg, "", " ")
default:
return nil, fmt.Errorf(`invalid serialization`)
}
}
// Decrypt takes encrypted payload, and information required to decrypt the
// payload (e.g. the key encryption algorithm and the corresponding
// key to decrypt the JWE message) in its optional arguments. See
// the examples and list of options that return a DecryptOption for possible
// values. Upon successful decryptiond returns the decrypted payload.
//
// The JWE message can be either compact or full JSON format.
//
// When using `jwe.WithKeyEncryptionAlgorithm()`, you can pass a `jwa.KeyAlgorithm`
// for convenience: this is mainly to allow you to directly pass the result of `(jwk.Key).Algorithm()`.
// However, do note that while `(jwk.Key).Algorithm()` could very well contain key encryption
// algorithms, it could also contain other types of values, such as _signature algorithms_.
// In order for `jwe.Decrypt` to work properly, the `alg` parameter must be of type
// `jwa.KeyEncryptionAlgorithm` or otherwise it will cause an error.
//
// When using `jwe.WithKey()`, the value must be a private key.
// It can be either in its raw format (e.g. *rsa.PrivateKey) or a jwk.Key
//
// When the encrypted message is also compressed, the decompressed payload must be
// smaller than the size specified by the `jwe.WithMaxDecompressBufferSize` setting,
// which defaults to 10MB. If the decompressed payload is larger than this size,
// an error is returned.
//
// You can opt to change the MaxDecompressBufferSize setting globally, or on a
// per-call basis by passing the `jwe.WithMaxDecompressBufferSize` option to
// either `jwe.Settings()` or `jwe.Decrypt()`:
//
// jwe.Settings(jwe.WithMaxDecompressBufferSize(10*1024*1024)) // changes value globally
// jwe.Decrypt(..., jwe.WithMaxDecompressBufferSize(250*1024)) // changes just for this call
func Decrypt(buf []byte, options ...DecryptOption) ([]byte, error) {
dc := decryptContextPool.Get()
defer decryptContextPool.Put(dc)
if err := dc.ProcessOptions(options); err != nil {
return nil, decryptError{fmt.Errorf(`jwe.Decrypt: failed to process options: %w`, err)}
}
ret, err := dc.DecryptMessage(buf)
if err != nil {
return nil, decryptError{fmt.Errorf(`jwe.Decrypt: %w`, err)}
}
return ret, nil
}
// Parse parses the JWE message into a Message object. The JWE message
// can be either compact or full JSON format.
//
// Parse() currently does not take any options, but the API accepts it
// in anticipation of future addition.
func Parse(buf []byte, _ ...ParseOption) (*Message, error) {
return parseJSONOrCompact(buf, false)
}
// errors are wrapped within this function, because we call it directly
// from Decrypt as well.
func parseJSONOrCompact(buf []byte, storeProtectedHeaders bool) (*Message, error) {
buf = bytes.TrimSpace(buf)
if len(buf) == 0 {
return nil, parseError{fmt.Errorf(`jwe.Parse: empty buffer`)}
}
var msg *Message
var err error
if buf[0] == tokens.OpenCurlyBracket {
msg, err = parseJSON(buf, storeProtectedHeaders)
} else {
msg, err = parseCompact(buf, storeProtectedHeaders)
}
if err != nil {
return nil, parseError{fmt.Errorf(`jwe.Parse: %w`, err)}
}
return msg, nil
}
// ParseString is the same as Parse, but takes a string.
func ParseString(s string) (*Message, error) {
msg, err := Parse([]byte(s))
if err != nil {
return nil, parseError{fmt.Errorf(`jwe.ParseString: %w`, err)}
}
return msg, nil
}
// ParseReader is the same as Parse, but takes an io.Reader.
func ParseReader(src io.Reader) (*Message, error) {
buf, err := io.ReadAll(src)
if err != nil {
return nil, parseError{fmt.Errorf(`jwe.ParseReader: failed to read from io.Reader: %w`, err)}
}
msg, err := Parse(buf)
if err != nil {
return nil, parseError{fmt.Errorf(`jwe.ParseReader: %w`, err)}
}
return msg, nil
}
func parseJSON(buf []byte, storeProtectedHeaders bool) (*Message, error) {
m := NewMessage()
m.storeProtectedHeaders = storeProtectedHeaders
if err := json.Unmarshal(buf, &m); err != nil {
return nil, fmt.Errorf(`failed to parse JSON: %w`, err)
}
return m, nil
}
func parseCompact(buf []byte, storeProtectedHeaders bool) (*Message, error) {
var parts [5][]byte
var ok bool
for i := range 4 {
parts[i], buf, ok = bytes.Cut(buf, []byte{tokens.Period})
if !ok {
return nil, fmt.Errorf(`compact JWE format must have five parts (%d)`, i+1)
}
}
// Validate that the last part does not contain more dots
if bytes.ContainsRune(buf, tokens.Period) {
return nil, errors.New(`compact JWE format must have five parts, not more`)
}
parts[4] = buf
hdrbuf, err := base64.Decode(parts[0])
if err != nil {
return nil, fmt.Errorf(`failed to parse first part of compact form: %w`, err)
}
protected := NewHeaders()
if err := json.Unmarshal(hdrbuf, protected); err != nil {
return nil, fmt.Errorf(`failed to parse header JSON: %w`, err)
}
ivbuf, err := base64.Decode(parts[2])
if err != nil {
return nil, fmt.Errorf(`failed to base64 decode iv: %w`, err)
}
ctbuf, err := base64.Decode(parts[3])
if err != nil {
return nil, fmt.Errorf(`failed to base64 decode content: %w`, err)
}
tagbuf, err := base64.Decode(parts[4])
if err != nil {
return nil, fmt.Errorf(`failed to base64 decode tag: %w`, err)
}
m := NewMessage()
if err := m.Set(CipherTextKey, ctbuf); err != nil {
return nil, fmt.Errorf(`failed to set %s: %w`, CipherTextKey, err)
}
if err := m.Set(InitializationVectorKey, ivbuf); err != nil {
return nil, fmt.Errorf(`failed to set %s: %w`, InitializationVectorKey, err)
}
if err := m.Set(ProtectedHeadersKey, protected); err != nil {
return nil, fmt.Errorf(`failed to set %s: %w`, ProtectedHeadersKey, err)
}
if err := m.makeDummyRecipient(string(parts[1]), protected); err != nil {
return nil, fmt.Errorf(`failed to setup recipient: %w`, err)
}
if err := m.Set(TagKey, tagbuf); err != nil {
return nil, fmt.Errorf(`failed to set %s: %w`, TagKey, err)
}
if storeProtectedHeaders {
// This is later used for decryption.
m.rawProtectedHeaders = parts[0]
}
return m, nil
}
type CustomDecoder = json.CustomDecoder
type CustomDecodeFunc = json.CustomDecodeFunc
// RegisterCustomField allows users to specify that a private field
// be decoded as an instance of the specified type. This option has
// a global effect.
//
// For example, suppose you have a custom field `x-birthday`, which
// you want to represent as a string formatted in RFC3339 in JSON,
// but want it back as `time.Time`.
//
// In such case you would register a custom field as follows
//
// jws.RegisterCustomField(`x-birthday`, time.Time{})
//
// Then you can use a `time.Time` variable to extract the value
// of `x-birthday` field, instead of having to use `any`
// and later convert it to `time.Time`
//
// var bday time.Time
// _ = hdr.Get(`x-birthday`, &bday)
//
// If you need a more fine-tuned control over the decoding process,
// you can register a `CustomDecoder`. For example, below shows
// how to register a decoder that can parse RFC1123 format string:
//
// jwe.RegisterCustomField(`x-birthday`, jwe.CustomDecodeFunc(func(data []byte) (any, error) {
// return time.Parse(time.RFC1123, string(data))
// }))
//
// Please note that use of custom fields can be problematic if you
// are using a library that does not implement MarshalJSON/UnmarshalJSON
// and you try to roundtrip from an object to JSON, and then back to an object.
// For example, in the above example, you can _parse_ time values formatted
// in the format specified in RFC822, but when you convert an object into
// JSON, it will be formatted in RFC3339, because that's what `time.Time`
// likes to do. To avoid this, it's always better to use a custom type
// that wraps your desired type (in this case `time.Time`) and implement
// MarshalJSON and UnmashalJSON.
func RegisterCustomField(name string, object any) {
registry.Register(name, object)
}