Initial QSfera import
This commit is contained in:
+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
|
||||
}
|
||||
Reference in New Issue
Block a user