Initial QSfera import
This commit is contained in:
+70
@@ -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
@@ -0,0 +1,94 @@
|
||||
# JWE [](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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
+22
@@ -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__"],
|
||||
)
|
||||
+268
@@ -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
|
||||
}
|
||||
+34
@@ -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__"],
|
||||
)
|
||||
+169
@@ -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
|
||||
}
|
||||
+32
@@ -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
|
||||
}
|
||||
+24
@@ -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__"],
|
||||
)
|
||||
+66
@@ -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
|
||||
}
|
||||
Generated
Vendored
+21
@@ -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__"],
|
||||
)
|
||||
Generated
Vendored
+43
@@ -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
|
||||
}
|
||||
Generated
Vendored
+20
@@ -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
|
||||
}
|
||||
+24
@@ -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__"],
|
||||
)
|
||||
+40
@@ -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
|
||||
}
|
||||
+139
@@ -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
@@ -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)
|
||||
}
|
||||
+1118
File diff suppressed because it is too large
Load Diff
+43
@@ -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",
|
||||
],
|
||||
)
|
||||
+34
@@ -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
@@ -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
|
||||
+177
@@ -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{})
|
||||
}
|
||||
+91
@@ -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
|
||||
}
|
||||
+147
@@ -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
|
||||
}
|
||||
+115
@@ -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
|
||||
}
|
||||
+70
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)}
|
||||
}
|
||||
Reference in New Issue
Block a user