Payload encryption with JWE

Hi @dchiesa1 

 

We intending on encrypting a JSON payload with a dynamically generated CEK using AES256GCM and then encrypting the CEK using a RSA-OAEP-256 algorithm (public-private key pair) and sending it to the Apigee proxy as an encrypted JWT.

Does the verifyEncryptedJWT callout https://github.com/DinoChiesa/Apigee-CustomPolicy-EncryptedJWT also decrypt the payload as well apart from decrypting the key? 

Additionally what would be the recommended approach to achieve this through Apigee?

 

Thank you

Solved Solved
1 10 1,558
1 ACCEPTED SOLUTION

The backend response sent back to Apigee which is encrypted using the GenerateJWT callout and sent back to the client.

You can do that. But with crypto, in particular with JWE and JWT, I caution you to take extra care with the terms you use, so you are clear on what's happening.

There is a "family" of IETF specifications called JSON Object Signing and Encryption, under the acronym "JOSE".

JOSE includes these specs:

Specification Quick summary Apigee support?
RFC 7515 - JWS JSON Web Signature
Describes how to use JSON to encode a signature of *any* data stream.
Built-in policies
RFC 7516 - JWE JSON Web Encryption
Describes how to use JSON to encrypt and wrap any data stream. (XML or PDF or PNG or JSON or whatever)
via a Java callout. Support only for RSA-based encryption algorithms.
RFC 7519 - JWT JSON Web Token
Describes how to use either JWS or JWE to sign or encrypt a JSON payload, and also prescribes the semantics of some "well known" claims or properties within that JSON payload, like iss for "issuer" or "issuing party", "aud" for "audience", "sub" for Subject, "exp" for expiry, and so on.
Built-in policies. Works for either signed or encrypted tokens. Supports all the encryption algorithms in RFC 7518.
RFC 7518 - JWA JSON Web Algorithms
Specifies algorithms for signing or encrypting.
Built in support for all of these, via the JWS and JWT policies.

The "JSON" in the names of these specifications does not imply that the signing or encrypting can be done only on JSON objects. Instead, it implies that the JSON is used to express encoded metadata about the signature or ciphertext. For example you could use JWS to sign a JSON, or a PDF, or a .PNG, or even an XML document. Likewise with JWE you can encrypt a JSON, or XML, or PDF etc. The result (either a signature or a ciphertext) is "wrapped in" a JSON wrapper that encodes the signed or encrypted thing (maybe an XML document etc) as well as a JSON header that describes how the signature or ciphertext was computed, and the signature or ciphertext bytestream itself.

Under the banner of JWS, one option is to sign a JSON payload. The result of that, we can call a JWT. Similarly, under JWE, one option is to encrypt a JSON payload. The result of that operation we can call an encrypted JWT. These "Tokens" are distinct from the general case only in that the payload itself is JSON. Whether using signing or encryption, if the signed or encrypted payload itself is a JSON object, we can call the result a JWT.

So you see, Apigee has builtin support for "encrypted JWT", which is to say, for JWT that are encrypted according to the JWE spec. Another way to say it is, Apigee has support for JWE, only if the encrypted thing is a JSON object. But Apigee does not have builtin support for JWE in general. For that you need to use the external callout, which is limited in that it supports only RSA-based crypto algorithms.

I hope this is all clear.

So getting back to your goal:

The intention is for the client to encrypt the entire payload which gets decrypted in Apigee and passed unencrypted to the backend. The backend response sent back to Apigee which is encrypted using the GenerateJWT callout and sent back to the client.

No problem. If you use "encrypted JWT", then you can use the builtin policies to do the things you want in the Apigee proxy:

  • VerifyJWT on the request flow, which will decrypt the inbound payload, and provide to you (via context variable) the plaintext JSON
  • GenerateJWT on the response flow, which will encrypt a payload, and provide to you the encoded encrypted JWT

If you use JWE (which implies that the client's original payload is not JSON), then within Apigee you must use the Java callout to do those things. It works the same way.

  • Use the callout class com.google.apigee.callouts.VerifyJwe for on the request flow,
  • use com.google.apigee.callouts.GenerateJwe on the response flow.

You earlier said

We intending on encrypting a JSON payload with a dynamically generated CEK using AES256GCM and then encrypting the CEK using a RSA-OAEP-256 algorithm (public-private key pair) and sending it to the Apigee proxy as an encrypted JWT.

That sounds to me like an encrypted JWT, which means you can use the builtin policies in Apigee to accomplish that work. The builtin policy supports A256GCM for the content-encryption algorithm and RSA-OAEP-256 for the key-encryption algorithm. You do not need to resort to the Encrypted JWT callout. On the request flow, the configuration would look something like this:

 

<VerifyJWT name='VJWT-encrypted-token'>
  <!-- You must specify AT LEAST the key-encryption algorithm -->
  <Algorithms>
    <Key>RSA-OAEP-256</Key>
    <Content>A256GCM</Content>
  </Algorithms>

  <!-- specify your private RSA key here -->
  <PrivateKey>
    <Value ref='private.private_rsa_key'/>
  </PrivateKey>

  <!-- 
  If the inbound JWT is in a variable specify it here. 
  If the inbound JWT is in the Authorization header, omit the following line.
  -->
  <Source>inbound.jwt</Source>

  <!-- The policy will implicitly verify the expiration of the JWT -->

  <!-- Optional:  You can also verify specific claims if you like -->
  <Subject>dino@example.org</Subject>
  <Issuer>whatever-issuer-you-like</Issuer>

</VerifyJWT>

 

On the Response flow (generating an encrypted JWT) it would look like this:

 

<GenerateJWT name='GJWT-response'>
  <Algorithms>
    <Key>RSA-OAEP-256</Key>
    <Content>A256GCM</Content>
  </Algorithms>

  <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
  <PublicKey>
    <Value ref='public.key_pem'/>
  </PublicKey>

  <Subject>subject-here</Subject>
  <Issuer>issuer-here</Issuer>
  <Audience>audience-here</Audience>
  <AdditionalClaims>
    <Claim name='additional-claim'>additional-claim-value</Claim>
  </AdditionalClaims>
  <AdditionalHeaders>
    <Claim name='hdr1'>hdr1</Claim>
  </AdditionalHeaders>
  <OutputVariable>output_variable</OutputVariable>

</GenerateJWT>

 

[EDIT] there is a way to pass a JSON blob as the payload to the GenerateJWT policy, but it is a little obscure. If you have the entire JSON that you want to encrypt, then you need to use something like this:

 

<GenerateJWT name="GenerateJWT-3">
  <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
  <Algorithms>
    <Key>RSA-OAEP-256</Key>
    <Content>A256GCM</Content>
  </Algorithms>
  <PublicKey>
    <Value ref="rsa_public_key"/>
  </PublicKey>

  <!-- specify the context variable containing the desired payload here -->
  <AdditionalClaims ref='raw-jwt-payload'/>

  <OutputVariable>output_jwt</OutputVariable>
</GenerateJWT>

 

View solution in original post

10 REPLIES 10

Yes.Java callout verify and set flow variables. Follow the instructions & give it a try if not tested.

Are you doing something related  to RFC 7516 - https://datatracker.ietf.org/doc/html/rfc7516? What's the use-case to learn more ?

Hi, 

Thank you for your insight. Yes, we are using the RFC mentioned. The intention is for the client to encrypt the entire payload which gets decrypted in Apigee and passed unencrypted to the backend. The backend response sent back to Apigee which is encrypted using the GenerateJWT callout and sent back to the client.

The backend response sent back to Apigee which is encrypted using the GenerateJWT callout and sent back to the client.

You can do that. But with crypto, in particular with JWE and JWT, I caution you to take extra care with the terms you use, so you are clear on what's happening.

There is a "family" of IETF specifications called JSON Object Signing and Encryption, under the acronym "JOSE".

JOSE includes these specs:

Specification Quick summary Apigee support?
RFC 7515 - JWS JSON Web Signature
Describes how to use JSON to encode a signature of *any* data stream.
Built-in policies
RFC 7516 - JWE JSON Web Encryption
Describes how to use JSON to encrypt and wrap any data stream. (XML or PDF or PNG or JSON or whatever)
via a Java callout. Support only for RSA-based encryption algorithms.
RFC 7519 - JWT JSON Web Token
Describes how to use either JWS or JWE to sign or encrypt a JSON payload, and also prescribes the semantics of some "well known" claims or properties within that JSON payload, like iss for "issuer" or "issuing party", "aud" for "audience", "sub" for Subject, "exp" for expiry, and so on.
Built-in policies. Works for either signed or encrypted tokens. Supports all the encryption algorithms in RFC 7518.
RFC 7518 - JWA JSON Web Algorithms
Specifies algorithms for signing or encrypting.
Built in support for all of these, via the JWS and JWT policies.

The "JSON" in the names of these specifications does not imply that the signing or encrypting can be done only on JSON objects. Instead, it implies that the JSON is used to express encoded metadata about the signature or ciphertext. For example you could use JWS to sign a JSON, or a PDF, or a .PNG, or even an XML document. Likewise with JWE you can encrypt a JSON, or XML, or PDF etc. The result (either a signature or a ciphertext) is "wrapped in" a JSON wrapper that encodes the signed or encrypted thing (maybe an XML document etc) as well as a JSON header that describes how the signature or ciphertext was computed, and the signature or ciphertext bytestream itself.

Under the banner of JWS, one option is to sign a JSON payload. The result of that, we can call a JWT. Similarly, under JWE, one option is to encrypt a JSON payload. The result of that operation we can call an encrypted JWT. These "Tokens" are distinct from the general case only in that the payload itself is JSON. Whether using signing or encryption, if the signed or encrypted payload itself is a JSON object, we can call the result a JWT.

So you see, Apigee has builtin support for "encrypted JWT", which is to say, for JWT that are encrypted according to the JWE spec. Another way to say it is, Apigee has support for JWE, only if the encrypted thing is a JSON object. But Apigee does not have builtin support for JWE in general. For that you need to use the external callout, which is limited in that it supports only RSA-based crypto algorithms.

I hope this is all clear.

So getting back to your goal:

The intention is for the client to encrypt the entire payload which gets decrypted in Apigee and passed unencrypted to the backend. The backend response sent back to Apigee which is encrypted using the GenerateJWT callout and sent back to the client.

No problem. If you use "encrypted JWT", then you can use the builtin policies to do the things you want in the Apigee proxy:

  • VerifyJWT on the request flow, which will decrypt the inbound payload, and provide to you (via context variable) the plaintext JSON
  • GenerateJWT on the response flow, which will encrypt a payload, and provide to you the encoded encrypted JWT

If you use JWE (which implies that the client's original payload is not JSON), then within Apigee you must use the Java callout to do those things. It works the same way.

  • Use the callout class com.google.apigee.callouts.VerifyJwe for on the request flow,
  • use com.google.apigee.callouts.GenerateJwe on the response flow.

You earlier said

We intending on encrypting a JSON payload with a dynamically generated CEK using AES256GCM and then encrypting the CEK using a RSA-OAEP-256 algorithm (public-private key pair) and sending it to the Apigee proxy as an encrypted JWT.

That sounds to me like an encrypted JWT, which means you can use the builtin policies in Apigee to accomplish that work. The builtin policy supports A256GCM for the content-encryption algorithm and RSA-OAEP-256 for the key-encryption algorithm. You do not need to resort to the Encrypted JWT callout. On the request flow, the configuration would look something like this:

 

<VerifyJWT name='VJWT-encrypted-token'>
  <!-- You must specify AT LEAST the key-encryption algorithm -->
  <Algorithms>
    <Key>RSA-OAEP-256</Key>
    <Content>A256GCM</Content>
  </Algorithms>

  <!-- specify your private RSA key here -->
  <PrivateKey>
    <Value ref='private.private_rsa_key'/>
  </PrivateKey>

  <!-- 
  If the inbound JWT is in a variable specify it here. 
  If the inbound JWT is in the Authorization header, omit the following line.
  -->
  <Source>inbound.jwt</Source>

  <!-- The policy will implicitly verify the expiration of the JWT -->

  <!-- Optional:  You can also verify specific claims if you like -->
  <Subject>dino@example.org</Subject>
  <Issuer>whatever-issuer-you-like</Issuer>

</VerifyJWT>

 

On the Response flow (generating an encrypted JWT) it would look like this:

 

<GenerateJWT name='GJWT-response'>
  <Algorithms>
    <Key>RSA-OAEP-256</Key>
    <Content>A256GCM</Content>
  </Algorithms>

  <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
  <PublicKey>
    <Value ref='public.key_pem'/>
  </PublicKey>

  <Subject>subject-here</Subject>
  <Issuer>issuer-here</Issuer>
  <Audience>audience-here</Audience>
  <AdditionalClaims>
    <Claim name='additional-claim'>additional-claim-value</Claim>
  </AdditionalClaims>
  <AdditionalHeaders>
    <Claim name='hdr1'>hdr1</Claim>
  </AdditionalHeaders>
  <OutputVariable>output_variable</OutputVariable>

</GenerateJWT>

 

[EDIT] there is a way to pass a JSON blob as the payload to the GenerateJWT policy, but it is a little obscure. If you have the entire JSON that you want to encrypt, then you need to use something like this:

 

<GenerateJWT name="GenerateJWT-3">
  <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
  <Algorithms>
    <Key>RSA-OAEP-256</Key>
    <Content>A256GCM</Content>
  </Algorithms>
  <PublicKey>
    <Value ref="rsa_public_key"/>
  </PublicKey>

  <!-- specify the context variable containing the desired payload here -->
  <AdditionalClaims ref='raw-jwt-payload'/>

  <OutputVariable>output_jwt</OutputVariable>
</GenerateJWT>

 

Why does the last GenerateJWT policy have a private key and not a public key?

Sorry, what do you mean by "last" ??? 

GenerateJWT can generate signed or encrypted JWT. 

Assuming we are speaking of assymmetric algorithms, like RSA,

If the output JWT is to be Signed, then the policy needs a private key.  If the output JWT is to be Encrypted, then the policy needs a public key. 

MAkes sense?

Yep makes sense, was wondering why

<GenerateJWT name="GenerateJWT-3">
  <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
  <Algorithms>
    <Key>RSA-OAEP-256</Key>
    <Content>A256GCM</Content>
  </Algorithms>
  <PrivateKey>
    <Value ref="private.rsa_privatekey"/>
  </PrivateKey>

  <!-- specify the context variable containing the desired payload here -->
  <AdditionalClaims ref='raw-jwt-payload'/>

  <OutputVariable>output_jwt</OutputVariable>
</GenerateJWT>

had a private key instead of a public one. But now I understand for encryption we use public keys.  

Whoops - you are correct!  that GenerateJWT policy example should have a PublicKey, not PrivateKey. 

My bad! 

I've corrected the original. 

Correct. wanted to point out below reference to avoid confusion to the reader on the context.

For encrypted JWT, if you are using asymmetric keys for the Key-encryption algorithm, you use the public key for generation, and the private key for verification.

For signed JWT, if you are using asymmetric algorithms, it's the reverse: use the private key for generation and the public key for verification.

reference:

https://www.googlecloudcommunity.com/gc/Apigee/Does-APIGEE-supports-JWE-and-Encryption/td-p/17424

General RFC7516 section:

https://datatracker.ietf.org/doc/html/rfc7516#section-5

 

 

Thank you for your detailed response @dchiesa1. I have an additional question regarding the EncryptedJWT Java callout. Does it give us an option to explicitly set the content encryption key at the time of generation? 

Does it give us an option to explicitly set the content encryption key at the time of generation?

I think you are referring to the "direct" algorithm that is specified in JWA, is that right? If so, the answer is

  • The JWE Java callout handles only RSA -based encryption (RSA-OAEP or RSA-OAEP-256), and does not support the direct algorithm.
  • The builtin encrypted JWT policies (GenerateJWT, VerifyJWT) include support for the direct algorithm (either for generation or verification), as well as for the RSA, ECDH, AES, and PBES algorithms.