Get hands-on experience with 20+ free Google Cloud products and $300 in free credit for new customers.

ApigeeX - generate JWE error while using java callout (jar) for a signed JWT

Hi @dchiesa1 , We are seeing below error while we try to generate JWE for a signed payload. Is there anything that we are missing?

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<JavaCallout continueOnError="false" enabled="true" name="JC-GenerateJWE">
<DisplayName>JC-GenerateJWE</DisplayName>
<Properties>
<Property name="key-encryption">RSA-OAEP-256</Property>
<Property name="content-encryption">A256GCM</Property>
<Property name="payload">{signed_payload}</Property>
<Property name="expiry">1h</Property>
<!-- the context variable "my_public_key" must hold a PEM-encoded RSA public key -->
<Property name="public-key">{private.publickey}</Property>
</Properties>
<ClassName>com.google.apigee.callouts.GenerateEncryptedJwt</ClassName>
<ResourceURL>java://apigee-callout-encrypted-jwt-20250403.jar</ResourceURL>
</JavaCallout>

Error:

ejwt_exception: java.lang.IllegalStateException: unable to read anything when decoding public key

 ejwt_error : unable to read anything when decoding public key

Solved Solved
1 11 212
1 ACCEPTED SOLUTION


@raghunathapalle wrote:

response has three sections separated by period. Is there a way to base 64 decode the response by using the period as delimited ? we see 1. JOSE Header 2. JSON Payload & 3. JWS Signature in the response so how do we handle the base 64 decode, verify signature only for the JWS signature & send the json payload back to the client app?


Well it sounds like the response payload (decrypted) is ... a JWS or JWT.  Probably JWT.

You can use DecodeJWT to split and then base64-decode the header and payload.  (Or DecodeJWS if it is a JWS)  But neither of those policies verifies a signature , which you should do.  

So I would suggest that you try VerifyJWT on the 3-part dot-separated thing.  And somewhere in your environment, you need to have the information about  how to get the public key to verify the signature. I don't know how to solve that, but maybe you do.  

Assuming the public key is available on a JWKS endpoint, the configuration will look like

<VerifyJWT name='VJWT-1'>
  <Algorithm>RS256</Algorithm> <!-- or whatever is appropriate -->
  <Source>jwe_output</Source>
  <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
  <PublicKey>
    <JWKS uri="https://the.endpoint.for.your.jwks/keys"/>
  </PublicKey>
  <!--
     other elements here 
  -->
</VerifyJWT>

Good, I'm glad you're making progress!

View solution in original post

11 REPLIES 11

Hey there!

The exception message

 

ejwt_exception: java.lang.IllegalStateException: unable to read anything when decoding public key

 

...is pretty clear. What is contained in the variable private.publickey ? As the comment in the policy configuration states, the context variable must hold a PEM-encoded RSA public key. This will look something like this: 

dchiesa1_0-1746030580538.png

Is that what you have?

By the way you have the variable prefixed with private. But the public key need not be private. It's a public key. It's shareable. This private. prefix won't affect the behavior of the callout, and it is not related to the error you're seeing. Just something I thought I would mention.

Hey @dchiesa1 , We are storing the privatekey for signing & publickey for JWE in the KVM & retrieving below code for signing & it works. It is not working for JWE.

<GenerateJWS name="Generate-JWS">
  <DisplayName>Generate JWS</DisplayName>
  <Algorithm>RS256</Algorithm>
  <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
  <PrivateKey>
    <Value ref="private.signprivatekey"/>
  </PrivateKey>
  <Payload ref="request.content"/>
  <AdditionalHeaders>
    <Claim name="issuedby">apigeex</Claim>
  </AdditionalHeaders>
  <OutputVariable>signed_payload</OutputVariable>
</GenerateJWS>

and

<JavaCallout continueOnError="false" enabled="true" name="JC-GenerateJWE">
  <DisplayName>JC-GenerateJWE</DisplayName>
  <Properties>
    <Property name="key-encryption">RSA-OAEP-256</Property>
    <Property name="content-encryption">A256GCM</Property>
    <Property name="payload">{signed_payload}</Property>
    <Property name="expiry">1h</Property>
    <!-- the context variable "my_public_key" must hold a PEM-encoded RSA public
         key -->
    <Property name="public-key">{private.publickey}</Property>
  </Properties>
  <ClassName>com.google.apigee.callouts.GenerateEncryptedJwt</ClassName>
  <ResourceURL>java://apigee-callout-encrypted-jwt-20250403.jar</ResourceURL>
</JavaCallout>

do you think that the className that I used above is correct for generating JWE ? Also can I use

<Property name="public-key">

<Value ref="private.publickey"/>

</Property>

instead of  <Property name="public-key">{private.publickey}</Property> ?

 

ok but you did not answer my question. I asked you:

What is contained in the variable private.publickey ? 

Can you verify / check the contents of that variable?  The error is telling you that the Callout cannot find a public key there.  You have showed me another policy that works. But that other policy is not reading the  private.publickey variable.  You have not told me what is in that variable.  

The information I have is pointing to that variable. The custom policy is telling you that variable does not contain a public key of the form I showed. You need to check the variable. 

By the way, I recorded a screencast for this scenario Signed JWT wrapped in JWE,  a few weeks ago.


@raghunathapalle wrote:

do you think that the className that I used above is correct for generating JWE ?


First, We need to be careful about terms. JWE is a general thing, and encrypted JWT is a specific kind of JWE.  I've gone over this distinction before, here on the community site, and in YT screencasts I've recorded, I think like this one. Basically a JWE is an encrypted wrapper on "anything" and an encrypted JWT is a specific kind of JWE which is an encrypted wrapper on a JSON payload.  It's something like "All squares are rectangles; some rectangles are not squares."  All encrypted JWT are JWE; some JWE are encrypted JWT.  

The class name you are using, com.google.apigee.callouts.GenerateEncryptedJwt , comes from this custom Callout project.  When you use that, it generates an encrypted JWT, which is also a JWE.  I don't know if that's what you want. Only you can say. 

Even so , the GenerateEncryptedJwt and the GenerateJwe works the same with respect to de-serializing the public key.  The class name, regardless whether it is the one you want or not, is not the source of the problem you are observing.  


@raghunathapalle wrote:

Also can I use

<Property name="public-key">

<Value ref="private.publickey"/>

</Property>

instead of  <Property name="public-key">{private.publickey}</Property> ?


No.  The README for the Callout shows you how to do it.  Assuming the value of the  private.publickey variable is good, this is the correct configuration syntax: 

 

  <Property name="public-key">{private.publickey}</Property> 

 

 

thanks @dchiesa1 , Got the request flow working now. Able to send the request to backend with signing & encryption. Issue was with certificate that we were using. Once we extract PEM encoded publickey from certificate then I got the encryption working correctly with the existing jar provided by you. 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<JavaCallout continueOnError="false" enabled="true" name="JC-GenerateJWE">
<DisplayName>JC-GenerateJWE</DisplayName>
<Properties>
<Property name="key-encryption">RSA-OAEP-256</Property>
<Property name="content-encryption">A256GCM</Property>
<Property name="debug">true</Property>
<Property name="payload">{signed_payload}</Property>
<!-- the context variable "my_public_key" must hold a PEM-encoded RSA public key -->
<Property name="public-key">{publickey}</Property>
</Properties>
<ClassName>com.google.apigee.callouts.GenerateJwe</ClassName>
<ResourceURL>java://apigee-callout-encrypted-jwt-20250403.jar</ResourceURL>
</JavaCallout>

still have tough time storing it the KVM & using it from kvm due to formatting issue since kvm expects the data in name/value pair json data.

Currently facing below error in response decryption which might be a payload related issue.

java.text.ParseException: Payload of JWE object is not a valid JSON object

config:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<JavaCallout continueOnError="false" enabled="true" name="JC-verifyJWE">
<DisplayName>JC-verifyJWE</DisplayName>
<Properties>
<Property name="key-encryption">RSA-OAEP-256</Property>
<Property name="content-encryption">A256GCM</Property>
<Property name="debug">true</Property>
<Property name="private-key">{encprivkey}</Property>
<Property name="source">encResp</Property>
</Properties>
<ClassName>com.google.apigee.callouts.VerifyEncryptedJwt</ClassName>
<ResourceURL>java://apigee-callout-encrypted-jwt-20250403.jar</ResourceURL>
</JavaCallout>

 

Raghu

It's good to hear you've sorted out the problem with the Certificate / Public Key. 

You've raised two followups.

  1. You're experiencing a challenge storing the public key in the KVM due to formatting issue since kvm expects the data in name/value pair json data.
  2. error in response decryption which might be a payload related issue.
    java.text.ParseException: Payload of JWE object is not a valid JSON object

On the first, I feel your pain.  This example bash script may help you.  The trick is to translate the newlines from the PEM file into backslash-N.  Like this: 

local file_contents=$(cat "$datafilename")
file_contents="${file_contents//$'\\\\n'/\\\\n}"

Try it and see if this solves your problem. 

On the second issue, the reason you see that error is because you are using VerifyEncryptedJwt to verify a JWE.  As I wrote in my first response on Thursday, We need to be careful about terms. JWE is a general thing, and encrypted JWT is a specific kind of JWE.  A JWE is an encrypted wrapper on "anything" and an encrypted JWT is a specific kind of JWE which is an encrypted wrapper on a JSON payload. 

In your case you produced a JWE, where the payload is a serialized signed JWT. The encrypted payload looks like this: 

dchiesa1_0-1746465898561.png

That, as you can see, is not JSON.  And you are using  VerifyEncryptedJwt , which is expecting a payload that is JSON.  When it finds a payload that is not JSON, it gives you the error you saw. 

The error message you are seeing "not a valid JSON object", is telling you "I was expecting a JSON and did not find one".  If you would like to verify /decrypt the thing you produced, then you need to use the custom Java callout with VerifyJwe . This is from the README for the custom Callout:

dchiesa1_1-1746466063493.png

Thanks @dchiesa1 . We are able to decrypt the response & response has three sections separated by period. Is there a way to base 64 decode the response by using the period as delimited ? we see 1. JOSE Header 2. JSON Payload & 3. JWS Signature in the response so how do we handle the base 64 decode, verify signature only for the JWS signature & send the json payload back to the client app? 


@raghunathapalle wrote:

response has three sections separated by period. Is there a way to base 64 decode the response by using the period as delimited ? we see 1. JOSE Header 2. JSON Payload & 3. JWS Signature in the response so how do we handle the base 64 decode, verify signature only for the JWS signature & send the json payload back to the client app?


Well it sounds like the response payload (decrypted) is ... a JWS or JWT.  Probably JWT.

You can use DecodeJWT to split and then base64-decode the header and payload.  (Or DecodeJWS if it is a JWS)  But neither of those policies verifies a signature , which you should do.  

So I would suggest that you try VerifyJWT on the 3-part dot-separated thing.  And somewhere in your environment, you need to have the information about  how to get the public key to verify the signature. I don't know how to solve that, but maybe you do.  

Assuming the public key is available on a JWKS endpoint, the configuration will look like

<VerifyJWT name='VJWT-1'>
  <Algorithm>RS256</Algorithm> <!-- or whatever is appropriate -->
  <Source>jwe_output</Source>
  <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
  <PublicKey>
    <JWKS uri="https://the.endpoint.for.your.jwks/keys"/>
  </PublicKey>
  <!--
     other elements here 
  -->
</VerifyJWT>

Good, I'm glad you're making progress!

thank you Dino @dchiesa1 . once I use Decode JWT policy , I am able to see the plain text response payload. It all works now . you made my day.

ha, I'm glad you got it to work! 

Please note: DecodeJWT does not verify the signature!   You should not rely on data that is revealed by DecodeJWT, unless and until you verify the signature, with VerifyJWT.  If you use VerifyJWT, then you will also get the payload in context variables, in the same way you get the data with DecodeJWT.   But VerifyJWT does the extra step of verifying the signature on that data, which assures you that it has not been modified since the signer signed it.  Ergo, after VerifyJWT, the data is trustworthy. 

Hi @dchiesa1 We are using VerifyJWS to verify the signature using public key from partner after DecodeJWT. Hope the data is trust worthy after the verifyJWS. We are seeing that apigeex response flow is skipping the decrypt , Decode & verify for error response from partner(backend). Is there a way to consider 400 & 500 as valid backend errors & do the decrypt , Decode & verify ?


@raghunathapalle wrote:

We are using VerifyJWS to verify the signature using public key from partner after DecodeJWT. Hope the data is trust worthy after the verifyJWS.


YES.  Either VerifyJWS or VerifyJWT will verify the signature, and after that policy succeeds, your API proxy can be assured that the claims are trustworthy.


@raghunathapalle wrote:

We are seeing that apigeex response flow is skipping the decrypt , Decode & verify for error response from partner(backend). Is there a way to consider 400 & 500 as valid backend errors & do the decrypt , Decode & verify ?


yes, surely. 

You can either

  • Allow Apigee to treat 400 or 500 as an error. This results in a Fault.  Then so the decrypt/decode/verify in a Fault Rule in the target endpoint. 
  • use success.codes on the property in the TargetEndpoint, to indicate 400 and 500 should not cause a fault. See here.

Your choice.

ps: you do not need to decode AND VerifyJWS.  In general, you should perform the DecodeJWS before VerifyJWS only if the DecodeJWS will give you some data from the header, than you need to perform the VerifyJWS.