Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
load("@rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "jwebb",
srcs = [
"content_cipher.go",
"key_decrypt_asymmetric.go",
"key_decrypt_symmetric.go",
"key_encrypt_asymmetric.go",
"key_encrypt_symmetric.go",
"key_encryption.go",
"keywrap.go",
],
importpath = "github.com/lestrrat-go/jwx/v3/jwe/jwebb",
visibility = ["//jwe:__subpackages__"],
deps = [
"//internal/keyconv",
"//internal/pool",
"//jwe/internal/cipher",
"//jwe/internal/concatkdf",
"//jwe/internal/content_crypt",
"//jwe/internal/keygen",
"//internal/tokens",
"@org_golang_x_crypto//pbkdf2",
],
)
go_test(
name = "jwebb_test",
srcs = [
"decrypt_test.go",
"jwebb_test.go",
"keywrap_test.go",
],
embed = [":jwebb"],
deps = [
"//internal/jwxtest",
"//jwa",
"//jwe/internal/keygen",
"//internal/tokens",
"@com_github_stretchr_testify//require",
],
)
@@ -0,0 +1,34 @@
package jwebb
import (
"fmt"
"github.com/lestrrat-go/jwx/v3/internal/tokens"
"github.com/lestrrat-go/jwx/v3/jwe/internal/cipher"
"github.com/lestrrat-go/jwx/v3/jwe/internal/content_crypt"
)
// ContentEncryptionIsSupported checks if the content encryption algorithm is supported
func ContentEncryptionIsSupported(alg string) bool {
switch alg {
case tokens.A128GCM, tokens.A192GCM, tokens.A256GCM,
tokens.A128CBC_HS256, tokens.A192CBC_HS384, tokens.A256CBC_HS512:
return true
default:
return false
}
}
// CreateContentCipher creates a content encryption cipher for the given algorithm string
func CreateContentCipher(alg string) (content_crypt.Cipher, error) {
if !ContentEncryptionIsSupported(alg) {
return nil, fmt.Errorf(`invalid content cipher algorithm (%s)`, alg)
}
cipher, err := cipher.NewAES(alg)
if err != nil {
return nil, fmt.Errorf(`failed to build content cipher for %s: %w`, alg, err)
}
return cipher, nil
}
+15
View File
@@ -0,0 +1,15 @@
// Package jwebb provides the building blocks (hence the name "bb") for JWE operations.
// It should be thought of as a low-level API, almost akin to internal packages
// that should not be used directly by users of the jwx package. However, these exist
// to provide a more efficient way to perform JWE operations without the overhead of
// the higher-level jwe package to power-users who know what they are doing.
//
// This package is currently considered EXPERIMENTAL, and the API may change
// without notice. It is not recommended to use this package unless you are
// fully aware of the implications of using it.
//
// All bb packages in jwx follow the same design principles:
// 1. Does minimal checking of input parameters (for performance); callers need to ensure that the parameters are valid.
// 2. All exported functions are stringly typed (i.e. they do not take any parameters unless they absolutely have to).
// 3. Does not rely on other public jwx packages (they are standalone, except for internal packages).
package jwebb
@@ -0,0 +1,177 @@
package jwebb
import (
"crypto"
"crypto/aes"
"crypto/ecdh"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/binary"
"fmt"
"hash"
"github.com/lestrrat-go/jwx/v3/internal/keyconv"
"github.com/lestrrat-go/jwx/v3/internal/tokens"
"github.com/lestrrat-go/jwx/v3/jwe/internal/concatkdf"
"github.com/lestrrat-go/jwx/v3/jwe/internal/keygen"
)
func contentEncryptionKeySize(ctalg string) (uint32, error) {
switch ctalg {
case tokens.A128GCM:
return tokens.KeySize16, nil
case tokens.A192GCM:
return tokens.KeySize24, nil
case tokens.A256GCM:
return tokens.KeySize32, nil
case tokens.A128CBC_HS256:
return tokens.KeySize32, nil
case tokens.A192CBC_HS384:
return tokens.KeySize48, nil
case tokens.A256CBC_HS512:
return tokens.KeySize64, nil
default:
return 0, fmt.Errorf(`unsupported content encryption algorithm %s`, ctalg)
}
}
func KeyEncryptionECDHESKeySize(alg, ctalg string) (string, uint32, bool, error) {
switch alg {
case tokens.ECDH_ES:
keysize, err := contentEncryptionKeySize(ctalg)
if err != nil {
return "", 0, false, err
}
return ctalg, keysize, false, nil
case tokens.ECDH_ES_A128KW:
return alg, tokens.KeySize16, true, nil
case tokens.ECDH_ES_A192KW:
return alg, tokens.KeySize24, true, nil
case tokens.ECDH_ES_A256KW:
return alg, tokens.KeySize32, true, nil
default:
return "", 0, false, fmt.Errorf(`unsupported key encryption algorithm %s`, alg)
}
}
func DeriveECDHES(alg string, apu, apv []byte, privkeyif, pubkeyif any, keysize uint32) ([]byte, error) {
pubinfo := make([]byte, 4)
binary.BigEndian.PutUint32(pubinfo, keysize*tokens.BitsPerByte)
var privkey *ecdh.PrivateKey
var pubkey *ecdh.PublicKey
if err := keyconv.ECDHPrivateKey(&privkey, privkeyif); err != nil {
return nil, fmt.Errorf(`jwebb.DeriveECDHES: %w`, err)
}
if err := keyconv.ECDHPublicKey(&pubkey, pubkeyif); err != nil {
return nil, fmt.Errorf(`jwebb.DeriveECDHES: %w`, err)
}
zBytes, err := privkey.ECDH(pubkey)
if err != nil {
return nil, fmt.Errorf(`jwebb.DeriveECDHES: unable to determine Z: %w`, err)
}
kdf := concatkdf.New(crypto.SHA256, []byte(alg), zBytes, apu, apv, pubinfo, []byte{})
key := make([]byte, keysize)
if _, err := kdf.Read(key); err != nil {
return nil, fmt.Errorf(`jwebb.DeriveECDHES: failed to read kdf: %w`, err)
}
return key, nil
}
func KeyDecryptECDHESKeyWrap(_, enckey []byte, alg string, apu, apv []byte, privkey, pubkey any, keysize uint32) ([]byte, error) {
key, err := DeriveECDHES(alg, apu, apv, privkey, pubkey, keysize)
if err != nil {
return nil, fmt.Errorf(`failed to derive ECDHES encryption key: %w`, err)
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, fmt.Errorf(`failed to create cipher for ECDH-ES key wrap: %w`, err)
}
return Unwrap(block, enckey)
}
func KeyDecryptECDHES(_, _ []byte, alg string, apu, apv []byte, privkey, pubkey any, keysize uint32) ([]byte, error) {
key, err := DeriveECDHES(alg, apu, apv, privkey, pubkey, keysize)
if err != nil {
return nil, fmt.Errorf(`failed to derive ECDHES encryption key: %w`, err)
}
return key, nil
}
// RSA key decryption functions
func KeyDecryptRSA15(_, enckey []byte, privkeyif any, keysize int) ([]byte, error) {
var privkey *rsa.PrivateKey
if err := keyconv.RSAPrivateKey(&privkey, privkeyif); err != nil {
return nil, fmt.Errorf(`jwebb.KeyDecryptRSA15: %w`, err)
}
// Perform some input validation.
expectedlen := privkey.PublicKey.N.BitLen() / tokens.BitsPerByte
if expectedlen != len(enckey) {
// Input size is incorrect, the encrypted payload should always match
// the size of the public modulus (e.g. using a 2048 bit key will
// produce 256 bytes of output). Reject this since it's invalid input.
return nil, fmt.Errorf(
"input size for key decrypt is incorrect (expected %d, got %d)",
expectedlen,
len(enckey),
)
}
// Generate a random CEK of the required size
bk, err := keygen.Random(keysize * tokens.RSAKeyGenMultiplier)
if err != nil {
return nil, fmt.Errorf(`failed to generate key`)
}
cek := bk.Bytes()
// Use a defer/recover pattern to handle potential panics from DecryptPKCS1v15SessionKey
defer func() {
// DecryptPKCS1v15SessionKey sometimes panics on an invalid payload
// because of an index out of bounds error, which we want to ignore.
// This has been fixed in Go 1.3.1 (released 2014/08/13), the recover()
// only exists for preventing crashes with unpatched versions.
// See: https://groups.google.com/forum/#!topic/golang-dev/7ihX6Y6kx9k
// See: https://code.google.com/p/go/source/detail?r=58ee390ff31602edb66af41ed10901ec95904d33
_ = recover()
}()
// When decrypting an RSA-PKCS1v1.5 payload, we must take precautions to
// prevent chosen-ciphertext attacks as described in RFC 3218, "Preventing
// the Million Message Attack on Cryptographic Message Syntax". We are
// therefore deliberately ignoring errors here.
_ = rsa.DecryptPKCS1v15SessionKey(rand.Reader, privkey, enckey, cek)
return cek, nil
}
func KeyDecryptRSAOAEP(_, enckey []byte, alg string, privkeyif any) ([]byte, error) {
var privkey *rsa.PrivateKey
if err := keyconv.RSAPrivateKey(&privkey, privkeyif); err != nil {
return nil, fmt.Errorf(`jwebb.KeyDecryptRSAOAEP: %w`, err)
}
var hash hash.Hash
switch alg {
case tokens.RSA_OAEP:
hash = sha1.New()
case tokens.RSA_OAEP_256:
hash = sha256.New()
case tokens.RSA_OAEP_384:
hash = sha512.New384()
case tokens.RSA_OAEP_512:
hash = sha512.New()
default:
return nil, fmt.Errorf(`failed to generate key encrypter for RSA-OAEP: RSA_OAEP/RSA_OAEP_256/RSA_OAEP_384/RSA_OAEP_512 required`)
}
return rsa.DecryptOAEP(hash, rand.Reader, privkey, enckey, []byte{})
}
@@ -0,0 +1,91 @@
package jwebb
import (
"crypto/aes"
cryptocipher "crypto/cipher"
"crypto/sha256"
"crypto/sha512"
"fmt"
"hash"
"golang.org/x/crypto/pbkdf2"
"github.com/lestrrat-go/jwx/v3/internal/tokens"
)
// AES key wrap decryption functions
// Use constants from tokens package
// No need to redefine them here
func KeyDecryptAESKW(_, enckey []byte, _ string, sharedkey []byte) ([]byte, error) {
block, err := aes.NewCipher(sharedkey)
if err != nil {
return nil, fmt.Errorf(`failed to create cipher from shared key: %w`, err)
}
cek, err := Unwrap(block, enckey)
if err != nil {
return nil, fmt.Errorf(`failed to unwrap data: %w`, err)
}
return cek, nil
}
func KeyDecryptDirect(_, _ []byte, _ string, cek []byte) ([]byte, error) {
return cek, nil
}
func KeyDecryptPBES2(_, enckey []byte, alg string, password []byte, salt []byte, count int) ([]byte, error) {
var hashFunc func() hash.Hash
var keylen int
switch alg {
case tokens.PBES2_HS256_A128KW:
hashFunc = sha256.New
keylen = tokens.KeySize16
case tokens.PBES2_HS384_A192KW:
hashFunc = sha512.New384
keylen = tokens.KeySize24
case tokens.PBES2_HS512_A256KW:
hashFunc = sha512.New
keylen = tokens.KeySize32
default:
return nil, fmt.Errorf(`unsupported PBES2 algorithm: %s`, alg)
}
// Derive key using PBKDF2
derivedKey := pbkdf2.Key(password, salt, count, keylen, hashFunc)
// Use the derived key for AES key wrap
return KeyDecryptAESKW(nil, enckey, alg, derivedKey)
}
func KeyDecryptAESGCMKW(recipientKey, _ []byte, _ string, sharedkey []byte, iv []byte, tag []byte) ([]byte, error) {
if len(iv) != tokens.GCMIVSize {
return nil, fmt.Errorf("GCM requires 96-bit iv, got %d", len(iv)*tokens.BitsPerByte)
}
if len(tag) != tokens.GCMTagSize {
return nil, fmt.Errorf("GCM requires 128-bit tag, got %d", len(tag)*tokens.BitsPerByte)
}
block, err := aes.NewCipher(sharedkey)
if err != nil {
return nil, fmt.Errorf(`failed to create new AES cipher: %w`, err)
}
aesgcm, err := cryptocipher.NewGCM(block)
if err != nil {
return nil, fmt.Errorf(`failed to create new GCM wrap: %w`, err)
}
// Combine recipient key and tag for GCM decryption
ciphertext := recipientKey[:]
ciphertext = append(ciphertext, tag...)
jek, err := aesgcm.Open(nil, iv, ciphertext, nil)
if err != nil {
return nil, fmt.Errorf(`failed to decode key: %w`, err)
}
return jek, nil
}
@@ -0,0 +1,147 @@
package jwebb
import (
"crypto/aes"
"crypto/ecdh"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"fmt"
"hash"
"github.com/lestrrat-go/jwx/v3/internal/tokens"
"github.com/lestrrat-go/jwx/v3/jwe/internal/keygen"
)
// KeyEncryptRSA15 encrypts the CEK using RSA PKCS#1 v1.5
func KeyEncryptRSA15(cek []byte, _ string, pubkey *rsa.PublicKey) (keygen.ByteSource, error) {
encrypted, err := rsa.EncryptPKCS1v15(rand.Reader, pubkey, cek)
if err != nil {
return nil, fmt.Errorf(`failed to encrypt using PKCS1v15: %w`, err)
}
return keygen.ByteKey(encrypted), nil
}
// KeyEncryptRSAOAEP encrypts the CEK using RSA OAEP
func KeyEncryptRSAOAEP(cek []byte, alg string, pubkey *rsa.PublicKey) (keygen.ByteSource, error) {
var hash hash.Hash
switch alg {
case tokens.RSA_OAEP:
hash = sha1.New()
case tokens.RSA_OAEP_256:
hash = sha256.New()
case tokens.RSA_OAEP_384:
hash = sha512.New384()
case tokens.RSA_OAEP_512:
hash = sha512.New()
default:
return nil, fmt.Errorf(`failed to generate key encrypter for RSA-OAEP: RSA_OAEP/RSA_OAEP_256/RSA_OAEP_384/RSA_OAEP_512 required`)
}
encrypted, err := rsa.EncryptOAEP(hash, rand.Reader, pubkey, cek, []byte{})
if err != nil {
return nil, fmt.Errorf(`failed to OAEP encrypt: %w`, err)
}
return keygen.ByteKey(encrypted), nil
}
// generateECDHESKeyECDSA generates the key material for ECDSA keys using ECDH-ES
func generateECDHESKeyECDSA(alg string, calg string, keysize uint32, pubkey *ecdsa.PublicKey, apu, apv []byte) (keygen.ByteWithECPublicKey, error) {
// Generate the key directly
kg, err := keygen.Ecdhes(alg, calg, int(keysize), pubkey, apu, apv)
if err != nil {
return keygen.ByteWithECPublicKey{}, fmt.Errorf(`failed to generate ECDSA key: %w`, err)
}
bwpk, ok := kg.(keygen.ByteWithECPublicKey)
if !ok {
return keygen.ByteWithECPublicKey{}, fmt.Errorf(`key generator generated invalid key (expected ByteWithECPublicKey)`)
}
return bwpk, nil
}
// generateECDHESKeyX25519 generates the key material for X25519 keys using ECDH-ES
func generateECDHESKeyX25519(alg string, calg string, keysize uint32, pubkey *ecdh.PublicKey) (keygen.ByteWithECPublicKey, error) {
// Generate the key directly
kg, err := keygen.X25519(alg, calg, int(keysize), pubkey)
if err != nil {
return keygen.ByteWithECPublicKey{}, fmt.Errorf(`failed to generate X25519 key: %w`, err)
}
bwpk, ok := kg.(keygen.ByteWithECPublicKey)
if !ok {
return keygen.ByteWithECPublicKey{}, fmt.Errorf(`key generator generated invalid key (expected ByteWithECPublicKey)`)
}
return bwpk, nil
}
// KeyEncryptECDHESKeyWrapECDSA encrypts the CEK using ECDH-ES with key wrapping for ECDSA keys
func KeyEncryptECDHESKeyWrapECDSA(cek []byte, alg string, apu, apv []byte, pubkey *ecdsa.PublicKey, keysize uint32, calg string) (keygen.ByteSource, error) {
bwpk, err := generateECDHESKeyECDSA(alg, calg, keysize, pubkey, apu, apv)
if err != nil {
return nil, err
}
// For key wrapping algorithms, wrap the CEK with the generated key
block, err := aes.NewCipher(bwpk.Bytes())
if err != nil {
return nil, fmt.Errorf(`failed to generate cipher from generated key: %w`, err)
}
jek, err := Wrap(block, cek)
if err != nil {
return nil, fmt.Errorf(`failed to wrap data: %w`, err)
}
bwpk.ByteKey = keygen.ByteKey(jek)
return bwpk, nil
}
// KeyEncryptECDHESKeyWrapX25519 encrypts the CEK using ECDH-ES with key wrapping for X25519 keys
func KeyEncryptECDHESKeyWrapX25519(cek []byte, alg string, _ []byte, _ []byte, pubkey *ecdh.PublicKey, keysize uint32, calg string) (keygen.ByteSource, error) {
bwpk, err := generateECDHESKeyX25519(alg, calg, keysize, pubkey)
if err != nil {
return nil, err
}
// For key wrapping algorithms, wrap the CEK with the generated key
block, err := aes.NewCipher(bwpk.Bytes())
if err != nil {
return nil, fmt.Errorf(`failed to generate cipher from generated key: %w`, err)
}
jek, err := Wrap(block, cek)
if err != nil {
return nil, fmt.Errorf(`failed to wrap data: %w`, err)
}
bwpk.ByteKey = keygen.ByteKey(jek)
return bwpk, nil
}
// KeyEncryptECDHESECDSA encrypts using ECDH-ES direct (no key wrapping) for ECDSA keys
func KeyEncryptECDHESECDSA(_ []byte, alg string, apu, apv []byte, pubkey *ecdsa.PublicKey, keysize uint32, calg string) (keygen.ByteSource, error) {
bwpk, err := generateECDHESKeyECDSA(alg, calg, keysize, pubkey, apu, apv)
if err != nil {
return nil, err
}
// For direct ECDH-ES, return the generated key directly
return bwpk, nil
}
// KeyEncryptECDHESX25519 encrypts using ECDH-ES direct (no key wrapping) for X25519 keys
func KeyEncryptECDHESX25519(_ []byte, alg string, _, _ []byte, pubkey *ecdh.PublicKey, keysize uint32, calg string) (keygen.ByteSource, error) {
bwpk, err := generateECDHESKeyX25519(alg, calg, keysize, pubkey)
if err != nil {
return nil, err
}
// For direct ECDH-ES, return the generated key directly
return bwpk, nil
}
@@ -0,0 +1,115 @@
package jwebb
import (
"crypto/aes"
cryptocipher "crypto/cipher"
"crypto/rand"
"crypto/sha256"
"crypto/sha512"
"fmt"
"hash"
"io"
"golang.org/x/crypto/pbkdf2"
"github.com/lestrrat-go/jwx/v3/internal/tokens"
"github.com/lestrrat-go/jwx/v3/jwe/internal/keygen"
)
// KeyEncryptAESKW encrypts the CEK using AES key wrap
func KeyEncryptAESKW(cek []byte, _ string, sharedkey []byte) (keygen.ByteSource, error) {
block, err := aes.NewCipher(sharedkey)
if err != nil {
return nil, fmt.Errorf(`failed to create cipher from shared key: %w`, err)
}
encrypted, err := Wrap(block, cek)
if err != nil {
return nil, fmt.Errorf(`failed to wrap data: %w`, err)
}
return keygen.ByteKey(encrypted), nil
}
// KeyEncryptDirect returns the CEK directly for DIRECT algorithm
func KeyEncryptDirect(_ []byte, _ string, sharedkey []byte) (keygen.ByteSource, error) {
return keygen.ByteKey(sharedkey), nil
}
// KeyEncryptPBES2 encrypts the CEK using PBES2 password-based encryption
func KeyEncryptPBES2(cek []byte, alg string, password []byte) (keygen.ByteSource, error) {
var hashFunc func() hash.Hash
var keylen int
switch alg {
case tokens.PBES2_HS256_A128KW:
hashFunc = sha256.New
keylen = tokens.KeySize16
case tokens.PBES2_HS384_A192KW:
hashFunc = sha512.New384
keylen = tokens.KeySize24
case tokens.PBES2_HS512_A256KW:
hashFunc = sha512.New
keylen = tokens.KeySize32
default:
return nil, fmt.Errorf(`unsupported PBES2 algorithm: %s`, alg)
}
count := tokens.PBES2DefaultIterations
salt := make([]byte, keylen)
_, err := io.ReadFull(rand.Reader, salt)
if err != nil {
return nil, fmt.Errorf(`failed to get random salt: %w`, err)
}
fullsalt := []byte(alg)
fullsalt = append(fullsalt, byte(tokens.PBES2NullByteSeparator))
fullsalt = append(fullsalt, salt...)
// Derive key using PBKDF2
derivedKey := pbkdf2.Key(password, fullsalt, count, keylen, hashFunc)
// Use the derived key for AES key wrap
block, err := aes.NewCipher(derivedKey)
if err != nil {
return nil, fmt.Errorf(`failed to create cipher from derived key: %w`, err)
}
encrypted, err := Wrap(block, cek)
if err != nil {
return nil, fmt.Errorf(`failed to wrap data: %w`, err)
}
return keygen.ByteWithSaltAndCount{
ByteKey: encrypted,
Salt: salt,
Count: count,
}, nil
}
// KeyEncryptAESGCMKW encrypts the CEK using AES GCM key wrap
func KeyEncryptAESGCMKW(cek []byte, _ string, sharedkey []byte) (keygen.ByteSource, error) {
block, err := aes.NewCipher(sharedkey)
if err != nil {
return nil, fmt.Errorf(`failed to create new AES cipher: %w`, err)
}
aesgcm, err := cryptocipher.NewGCM(block)
if err != nil {
return nil, fmt.Errorf(`failed to create new GCM wrap: %w`, err)
}
iv := make([]byte, aesgcm.NonceSize())
_, err = io.ReadFull(rand.Reader, iv)
if err != nil {
return nil, fmt.Errorf(`failed to get random iv: %w`, err)
}
encrypted := aesgcm.Seal(nil, iv, cek, nil)
tag := encrypted[len(encrypted)-aesgcm.Overhead():]
ciphertext := encrypted[:len(encrypted)-aesgcm.Overhead()]
return keygen.ByteWithIVAndTag{
ByteKey: ciphertext,
IV: iv,
Tag: tag,
}, nil
}
@@ -0,0 +1,70 @@
package jwebb
import (
"github.com/lestrrat-go/jwx/v3/internal/tokens"
)
// IsECDHES checks if the algorithm is an ECDH-ES based algorithm
func IsECDHES(alg string) bool {
switch alg {
case tokens.ECDH_ES, tokens.ECDH_ES_A128KW, tokens.ECDH_ES_A192KW, tokens.ECDH_ES_A256KW:
return true
default:
return false
}
}
// IsRSA15 checks if the algorithm is RSA1_5
func IsRSA15(alg string) bool {
return alg == tokens.RSA1_5
}
// IsRSAOAEP checks if the algorithm is an RSA-OAEP based algorithm
func IsRSAOAEP(alg string) bool {
switch alg {
case tokens.RSA_OAEP, tokens.RSA_OAEP_256, tokens.RSA_OAEP_384, tokens.RSA_OAEP_512:
return true
default:
return false
}
}
// IsAESKW checks if the algorithm is an AES key wrap algorithm
func IsAESKW(alg string) bool {
switch alg {
case tokens.A128KW, tokens.A192KW, tokens.A256KW:
return true
default:
return false
}
}
// IsAESGCMKW checks if the algorithm is an AES-GCM key wrap algorithm
func IsAESGCMKW(alg string) bool {
switch alg {
case tokens.A128GCMKW, tokens.A192GCMKW, tokens.A256GCMKW:
return true
default:
return false
}
}
// IsPBES2 checks if the algorithm is a PBES2 based algorithm
func IsPBES2(alg string) bool {
switch alg {
case tokens.PBES2_HS256_A128KW, tokens.PBES2_HS384_A192KW, tokens.PBES2_HS512_A256KW:
return true
default:
return false
}
}
// IsDirect checks if the algorithm is direct encryption
func IsDirect(alg string) bool {
return alg == tokens.DIRECT
}
// IsSymmetric checks if the algorithm is a symmetric key encryption algorithm
func IsSymmetric(alg string) bool {
return IsAESKW(alg) || IsAESGCMKW(alg) || IsPBES2(alg) || IsDirect(alg)
}
+110
View File
@@ -0,0 +1,110 @@
package jwebb
import (
"crypto/cipher"
"crypto/subtle"
"encoding/binary"
"fmt"
"github.com/lestrrat-go/jwx/v3/internal/pool"
"github.com/lestrrat-go/jwx/v3/internal/tokens"
)
var keywrapDefaultIV = []byte{0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6}
func Wrap(kek cipher.Block, cek []byte) ([]byte, error) {
if len(cek)%tokens.KeywrapBlockSize != 0 {
return nil, fmt.Errorf(`keywrap input must be %d byte blocks`, tokens.KeywrapBlockSize)
}
n := len(cek) / tokens.KeywrapChunkLen
r := make([][]byte, n)
for i := range n {
r[i] = make([]byte, tokens.KeywrapChunkLen)
copy(r[i], cek[i*tokens.KeywrapChunkLen:])
}
buffer := pool.ByteSlice().GetCapacity(tokens.KeywrapChunkLen * 2)
defer pool.ByteSlice().Put(buffer)
// the byte slice has the capacity, but len is 0
buffer = buffer[:tokens.KeywrapChunkLen*2]
tBytes := pool.ByteSlice().GetCapacity(tokens.KeywrapChunkLen)
defer pool.ByteSlice().Put(tBytes)
// the byte slice has the capacity, but len is 0
tBytes = tBytes[:tokens.KeywrapChunkLen]
copy(buffer, keywrapDefaultIV)
for t := range tokens.KeywrapRounds * n {
copy(buffer[tokens.KeywrapChunkLen:], r[t%n])
kek.Encrypt(buffer, buffer)
binary.BigEndian.PutUint64(tBytes, uint64(t+1))
for i := range tokens.KeywrapChunkLen {
buffer[i] = buffer[i] ^ tBytes[i]
}
copy(r[t%n], buffer[tokens.KeywrapChunkLen:])
}
out := make([]byte, (n+1)*tokens.KeywrapChunkLen)
copy(out, buffer[:tokens.KeywrapChunkLen])
for i := range r {
copy(out[(i+1)*tokens.KeywrapBlockSize:], r[i])
}
return out, nil
}
func Unwrap(block cipher.Block, ciphertxt []byte) ([]byte, error) {
if len(ciphertxt)%tokens.KeywrapChunkLen != 0 {
return nil, fmt.Errorf(`keyunwrap input must be %d byte blocks`, tokens.KeywrapChunkLen)
}
n := (len(ciphertxt) / tokens.KeywrapChunkLen) - 1
r := make([][]byte, n)
for i := range r {
r[i] = make([]byte, tokens.KeywrapChunkLen)
copy(r[i], ciphertxt[(i+1)*tokens.KeywrapChunkLen:])
}
buffer := pool.ByteSlice().GetCapacity(tokens.KeywrapChunkLen * 2)
defer pool.ByteSlice().Put(buffer)
// the byte slice has the capacity, but len is 0
buffer = buffer[:tokens.KeywrapChunkLen*2]
tBytes := pool.ByteSlice().GetCapacity(tokens.KeywrapChunkLen)
defer pool.ByteSlice().Put(tBytes)
// the byte slice has the capacity, but len is 0
tBytes = tBytes[:tokens.KeywrapChunkLen]
copy(buffer[:tokens.KeywrapChunkLen], ciphertxt[:tokens.KeywrapChunkLen])
for t := tokens.KeywrapRounds*n - 1; t >= 0; t-- {
binary.BigEndian.PutUint64(tBytes, uint64(t+1))
for i := range tokens.KeywrapChunkLen {
buffer[i] = buffer[i] ^ tBytes[i]
}
copy(buffer[tokens.KeywrapChunkLen:], r[t%n])
block.Decrypt(buffer, buffer)
copy(r[t%n], buffer[tokens.KeywrapChunkLen:])
}
if subtle.ConstantTimeCompare(buffer[:tokens.KeywrapChunkLen], keywrapDefaultIV) == 0 {
return nil, fmt.Errorf(`key unwrap: failed to unwrap key`)
}
out := make([]byte, n*tokens.KeywrapChunkLen)
for i := range r {
copy(out[i*tokens.KeywrapChunkLen:], r[i])
}
return out, nil
}