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! Go to 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 (link), which as of March 2022, is limited in that it supports only RSA-based crypto algorithms. (Edit: As of June 2024, that callout supports ECDH algorithms as well).
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:
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.
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>
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 (link), which as of March 2022, is limited in that it supports only RSA-based crypto algorithms. (Edit: As of June 2024, that callout supports ECDH algorithms as well).
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:
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.
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