Skip to content
This repository has been archived by the owner on Feb 27, 2023. It is now read-only.

Opaque key encryption/decryption v2 rebase #261

Merged
merged 11 commits into from
Sep 26, 2019
14 changes: 10 additions & 4 deletions crypter.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ func NewEncrypter(enc ContentEncryption, rcpt Recipient, opts *EncrypterOptions)
keyID, rawKey = encryptionKey.KeyID, encryptionKey.Key
case *JSONWebKey:
keyID, rawKey = encryptionKey.KeyID, encryptionKey.Key
case OpaqueKeyEncrypter:
keyID, rawKey = encryptionKey.KeyID(), encryptionKey
default:
rawKey = encryptionKey
}
Expand Down Expand Up @@ -267,9 +269,11 @@ func makeJWERecipient(alg KeyAlgorithm, encryptionKey interface{}) (recipientKey
recipient, err := makeJWERecipient(alg, encryptionKey.Key)
recipient.keyID = encryptionKey.KeyID
return recipient, err
default:
return recipientKeyInfo{}, ErrUnsupportedKeyType
}
if encrypter, ok := encryptionKey.(OpaqueKeyEncrypter); ok {
return newOpaqueKeyEncrypter(alg, encrypter)
}
return recipientKeyInfo{}, ErrUnsupportedKeyType
}

// newDecrypter creates an appropriate decrypter based on the key type
Expand All @@ -295,9 +299,11 @@ func newDecrypter(decryptionKey interface{}) (keyDecrypter, error) {
return newDecrypter(decryptionKey.Key)
case *JSONWebKey:
return newDecrypter(decryptionKey.Key)
default:
return nil, ErrUnsupportedKeyType
}
if okd, ok := decryptionKey.(OpaqueKeyDecrypter); ok {
return &opaqueKeyDecrypter{decrypter: okd}, nil
}
return nil, ErrUnsupportedKeyType
}

// Implementation of encrypt method producing a JWE object.
Expand Down
2 changes: 1 addition & 1 deletion jwt/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ func TestBuilderSignedAndEncrypted(t *testing.T) {
ExtraHeaders: map[jose.HeaderKey]interface{}{
jose.HeaderType: "JWT",
jose.HeaderContentType: "JWT",
"enc": "A128CBC-HS256",
"enc": "A128CBC-HS256",
},
}}, jwe.Headers)
if jws, err := jwe.Decrypt(testPrivRSAKey1); assert.NoError(t, err) {
Expand Down
61 changes: 61 additions & 0 deletions opaque.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,64 @@ type opaqueVerifier struct {
func (o *opaqueVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error {
return o.verifier.VerifyPayload(payload, signature, alg)
}

// OpaqueKeyEncrypter is an interface that supports encrypting keys with an opaque key.
type OpaqueKeyEncrypter interface {
// KeyID returns the kid
KeyID() string
// Algs returns a list of supported key encryption algorithms.
Algs() []KeyAlgorithm
// encryptKey encrypts the CEK using the given algorithm.
encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error)
}

type opaqueKeyEncrypter struct {
encrypter OpaqueKeyEncrypter
}

func newOpaqueKeyEncrypter(alg KeyAlgorithm, encrypter OpaqueKeyEncrypter) (recipientKeyInfo, error) {
var algSupported bool
for _, salg := range encrypter.Algs() {
if alg == salg {
algSupported = true
break
}
}
if !algSupported {
return recipientKeyInfo{}, ErrUnsupportedAlgorithm
}

return recipientKeyInfo{
keyID: encrypter.KeyID(),
keyAlg: alg,
keyEncrypter: &opaqueKeyEncrypter{
encrypter: encrypter,
},
}, nil
}

func (oke *opaqueKeyEncrypter) encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) {
return oke.encrypter.encryptKey(cek, alg)
}

//OpaqueKeyDecrypter is an interface that supports decrypting keys with an opaque key.
type OpaqueKeyDecrypter interface {
DecryptKey(encryptedKey []byte, header Header) ([]byte, error)
}

type opaqueKeyDecrypter struct {
decrypter OpaqueKeyDecrypter
}

func (okd *opaqueKeyDecrypter) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
mergedHeaders := rawHeader{}
mergedHeaders.merge(&headers)
mergedHeaders.merge(recipient.header)

header, err := mergedHeaders.sanitized()
if err != nil {
return nil, err
}

return okd.decrypter.DecryptKey(recipient.encryptedKey, header)
}
165 changes: 165 additions & 0 deletions opaque_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,78 @@ func (vw *verifyWrapper) VerifyPayload(payload []byte, signature []byte, alg Sig
return err
}

type keyEncryptWrapper struct {
kid string
wrapped keyEncrypter
algs []KeyAlgorithm
}

var _ = OpaqueKeyEncrypter(&keyEncryptWrapper{})

func (kew *keyEncryptWrapper) KeyID() string {
return kew.kid
}

func (kew *keyEncryptWrapper) Algs() []KeyAlgorithm {
return kew.algs
}

func (kew *keyEncryptWrapper) encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) {
info, err := kew.wrapped.encryptKey(cek, alg)
if err != nil {
return recipientInfo{}, err
}

return info, nil
}

type keyDecryptWrapper struct {
wrapped keyDecrypter
}

var _ = OpaqueKeyDecrypter(&keyDecryptWrapper{})

func (kdw *keyDecryptWrapper) DecryptKey(encryptedKey []byte, header Header) ([]byte, error) {
rawHeader := rawHeader{}

err := rawHeader.set(headerKeyID, header.KeyID)
if err != nil {
return nil, err
}
err = rawHeader.set(headerAlgorithm, header.Algorithm)
if err != nil {
return nil, err
}
err = rawHeader.set(headerNonce, header.Nonce)
if err != nil {
return nil, err
}
err = rawHeader.set(headerJWK, header.JSONWebKey)
if err != nil {
return nil, err
}
for k, v := range header.ExtraHeaders {
err = rawHeader.set(k, v)
if err != nil {
return nil, err
}
}

recipient := &recipientInfo{
encryptedKey: encryptedKey,
}

var generator randomKeyGenerator
cipher := getContentCipher(rawHeader.getEncryption())
if cipher != nil {
generator = randomKeyGenerator{
size: cipher.keySize(),
}
}

return kdw.wrapped.decryptKey(rawHeader, recipient, generator)
}

func TestRoundtripsJWSOpaque(t *testing.T) {
sigAlgs := []SignatureAlgorithm{RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512, EdDSA}

Expand Down Expand Up @@ -124,6 +196,29 @@ func makeOpaqueVerifier(t *testing.T, verificationKey []interface{}, alg Signatu
return &verifyWrapper{wrapped: verifiers}
}

func makeOpaqueKeyEncrypter(t *testing.T, signingKey interface{}, alg KeyAlgorithm, kid string) *keyEncryptWrapper {
rki, err := makeJWERecipient(alg, signingKey)
if err != nil {
t.Fatal(err, alg)
}
return &keyEncryptWrapper{
wrapped: rki.keyEncrypter,
algs: []KeyAlgorithm{alg},
kid: kid,
}
}

func makeOpaqueKeyDecrypter(t *testing.T, decryptionKey interface{}, alg KeyAlgorithm) *keyDecryptWrapper {
kd, err := newDecrypter(decryptionKey)
if err != nil {
t.Fatal(err)
}

return &keyDecryptWrapper{
wrapped: kd,
}
}

func TestOpaqueSignerKeyRotation(t *testing.T) {

sigAlgs := []SignatureAlgorithm{RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512, EdDSA}
Expand Down Expand Up @@ -190,3 +285,73 @@ func rtSerialize(t *testing.T, serializer func(*JSONWebSignature) (string, error
}
return sig
}

func TestOpaqueKeyRoundtripJWE(t *testing.T) {
keyAlgs := []KeyAlgorithm{
ECDH_ES_A128KW, ECDH_ES_A192KW, ECDH_ES_A256KW, A128KW, A192KW, A256KW,
RSA1_5, RSA_OAEP, RSA_OAEP_256, A128GCMKW, A192GCMKW, A256GCMKW,
PBES2_HS256_A128KW, PBES2_HS384_A192KW, PBES2_HS512_A256KW,
}
encAlgs := []ContentEncryption{A128GCM, A192GCM, A256GCM, A128CBC_HS256, A192CBC_HS384, A256CBC_HS512}
kid := "test-kid"

serializers := []func(*JSONWebEncryption) (string, error){
func(obj *JSONWebEncryption) (string, error) { return obj.CompactSerialize() },
func(obj *JSONWebEncryption) (string, error) { return obj.FullSerialize(), nil },
}

for _, alg := range keyAlgs {
for _, enc := range encAlgs {
for _, testKey := range generateTestKeys(alg, enc) {
for _, serializer := range serializers {
kew := makeOpaqueKeyEncrypter(t, testKey.enc, alg, kid)
encrypter, err := NewEncrypter(
enc,
Recipient{
Algorithm: alg,
Key: kew,
},
&EncrypterOptions{},
)
if err != nil {
t.Fatal(err, alg)
}

jwe, err := encrypter.Encrypt([]byte("foo bar"))
if err != nil {
t.Fatal(err, alg)
}

dw := makeOpaqueKeyDecrypter(t, testKey.dec, alg)
jwe = jweSerialize(t, serializer, jwe, dw)
if jwe.Header.KeyID != kid {
t.Errorf("expected jwe kid to equal %s but got %s", kid, jwe.Header.KeyID)
}

out, err := jwe.Decrypt(dw)
if err != nil {
t.Fatal(err, out)
}
if string(out) != "foo bar" {
t.Errorf("expected decrypted jwe to equal %s but got %s", "foo bar", string(out))
}
}
}
}
}
}

func jweSerialize(t *testing.T, serializer func(*JSONWebEncryption) (string, error), jwe *JSONWebEncryption, d OpaqueKeyDecrypter) *JSONWebEncryption {
b, err := serializer(jwe)
if err != nil {
t.Fatal(err)
}
jwe, err = ParseEncrypted(b)
if err != nil {
t.Fatal(err)
}
if _, err := jwe.Decrypt(d); err != nil {
t.Fatal(err)
}
return jwe
}