Sign and Encrypt JWT

how to sign and encrypt jwt tokens? In Apigee we can either sign the jwt or encrypt it, but we can't do both. So need to know is it achievable?

Is there any sample policy to do the same

Solved Solved
1 21 3,701
1 ACCEPTED SOLUTION

JWT are either signed, or encrypted.

You can create a signed JWT, and then encrypt THAT. This is a common pattern, it is sometimes called "nesting". See a discussion here. In Apigee, you can do this with two successive invocations of the GenerateJWT policy, passing the output of the first, as the input of the second.

You can create an encrypted JWT. I suppose you could sign the result of THAT. I have never seen this pattern, and I think it makes no sense. But, if you want to do it in Apigee, you could likewise with two successive invocations of the GenerateJWT policy. (Or you could use GenerateJWS for the 2nd invocation).

As far as I know there is no way to sign-and-encrypt the same thing in one step. The common pattern is encrypt(sign(payload)). In English, sign-then-encrypt. In more words, first sign, then encrypt the result of that.

Maybe you want to sign a payload, and also encrypt the same payload, and get two distinct JWT. That you can do, again, with two distinct invocations of GenerateJWT. This would be sensible only if you send these tokens to different systems. The signed JWT is easily decodable, so it makes no sense to send a signed JWT along with an encrypted JWT that uses the same payload. The signed JWT would allow the full payload to be easily read, thus rendering the encryption useless.

In some cases people want the encrypt(sign(payload)) pattern, but the output should be JWE. In other words they do not want an encrypted JWT, but instead want an output that is just a JWE. For that you would use this java callout for the 2nd step. There is no builtin policy in Apigee at this time that produces a generic JWE. We're working on it.

If this does not answer your question, you will need to clarify your requirements for us, without saying "Signed and encrypted at the same time". That's not sensible.

View solution in original post

21 REPLIES 21

Not applicable

Hi, I have gone through this document. But my question is I need to sign and encrypt the same payload. In Apigee it seems it can't be done through policies.For.ex. There are certain claims basis on which I need to create a jwt token. So at first, I need to sign the token, and then I need to encrypt the same. this is the requirement that I have.
Let me know if you got my question or not?

I got your point. I am actually using external JWT token generation and Apigee is only validating. The token is signed and encrypted. So, I think Apigee will also have the same option.

Hi, No, it is not like that. To Sign and Encrypt the JWT it is not done in APIGEE Policies. As we can only sign or Encrypt the JWT. So one operation can be done at a time.

So really need help with this. if there is any workaround to do so.

if any workaround is fine, then you can use java or javascript to encrypt.

ref: https://connect2id.com/products/nimbus-jose-jwt/examples/jwt-with-rsa-encryption

JWT are either signed, or encrypted.

You can create a signed JWT, and then encrypt THAT. This is a common pattern, it is sometimes called "nesting". See a discussion here. In Apigee, you can do this with two successive invocations of the GenerateJWT policy, passing the output of the first, as the input of the second.

You can create an encrypted JWT. I suppose you could sign the result of THAT. I have never seen this pattern, and I think it makes no sense. But, if you want to do it in Apigee, you could likewise with two successive invocations of the GenerateJWT policy. (Or you could use GenerateJWS for the 2nd invocation).

As far as I know there is no way to sign-and-encrypt the same thing in one step. The common pattern is encrypt(sign(payload)). In English, sign-then-encrypt. In more words, first sign, then encrypt the result of that.

Maybe you want to sign a payload, and also encrypt the same payload, and get two distinct JWT. That you can do, again, with two distinct invocations of GenerateJWT. This would be sensible only if you send these tokens to different systems. The signed JWT is easily decodable, so it makes no sense to send a signed JWT along with an encrypted JWT that uses the same payload. The signed JWT would allow the full payload to be easily read, thus rendering the encryption useless.

In some cases people want the encrypt(sign(payload)) pattern, but the output should be JWE. In other words they do not want an encrypted JWT, but instead want an output that is just a JWE. For that you would use this java callout for the 2nd step. There is no builtin policy in Apigee at this time that produces a generic JWE. We're working on it.

If this does not answer your question, you will need to clarify your requirements for us, without saying "Signed and encrypted at the same time". That's not sensible.

Hi Dino, Thank you for the Response.

I have already considered using two generate policies one will be used to Sign and another one to encrypt. But the Generate Policy doesn't have any SOURCE Element in which I can specify the Singed JWT Variable. Cause of which I am not able to encrypt the signed payload. But still i tried as below

First Signing

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<GenerateJWT async="false" continueOnError="false" enabled="true" name="Generate-JWT-1">
    <DisplayName>Generate JWT-1</DisplayName>
    <Algorithm>HS256</Algorithm>
    <SecretKey>
        <Value ref="private.signingKey"/>
    </SecretKey>
    <Subject>subject-subject</Subject>
    <Issuer>urn://apigee-edge-JWT-policy-test</Issuer>
    <Audience>audience1,audience2</Audience>
    <ExpiresIn>8h</ExpiresIn>
    <AdditionalClaims>
        <Claim name="additional-claim-name" type="string">additional-claim-value-goes-here</Claim>
    </AdditionalClaims>
    <OutputVariable>jwt-variable</OutputVariable>
</GenerateJWT>

Second Encrypting

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<GenerateJWT async="false" continueOnError="false" enabled="true" name="Generate-JWT-2">
    <DisplayName>Generate JWT-2</DisplayName>
    <Algorithms>
        <Key>RSA-OAEP-256</Key>
        <Content>A128CBC-HS256</Content>
    </Algorithms>
    <PublicKey>
        <JWKS ref="public.key"/>
        <Id>csrfJwtEncryptionKey</Id>
    </PublicKey>
    <Source ref="jwt-variable"/>
    <OutputVariable>jwt-variable1</OutputVariable>
</GenerateJWT>

I followed as shown but still, the signed payload is not getting encrypted, instead, the new jwt token with new jti claims is getting encrypted.

Let me know if this is what you were suggesting to try me out.

Thanks,

Did you read this part of my response?

In some cases people want the encrypt(sign(payload)) pattern, but the output should be JWE. In other words they do not want an encrypted JWT, but instead want an output that is just a JWE. For that you would use this java callout for the 2nd step. There is no builtin policy in Apigee at this time that produces a generic JWE. We're working on it.

Try the callout and see if that satisfies. It takes a Source (payload) property. The output is a jwE, not a jwT with claims like jti etc.

Let me know your thoughts.

Hi Dino,

Yes, I have used the java callout for creating a JWE. and also completed doing the same. The only issue is that I need to use JWKS for Encrypting the JWT for generating JWE.

Thanks,

ok, I've updated the repo for that Java callout. It now will retrieve the public key from a JWKS. Example for a JWE:

<JavaCallout name="Java-GenerateJWE-via-JWKS">
  <Properties>
    <Property name='key-encryption'>RSA-OAEP-256</Property>
    <Property name='content-encryption'>A256GCM</Property>
    <Property name='payload'>{demo_payload}</Property>
    <Property name='jwks-uri'>https://jwks-service.appspot.com/.well-known/jwks.json</Property>
    <Property name='key-id'>{my_key_id}</Property>
  </Properties>
  <ClassName>com.google.apigee.edgecallouts.GenerateJwe</ClassName>
  <ResourceURL>java://apigee-callout-encrypted-jwt-20200820.jar</ResourceURL>
</JavaCallout>

Example for a JWT:

<JavaCallout name="Java-GenerateJWT-via-JWKS">
  <Properties>
    <Property name='key-encryption'>RSA-OAEP-256</Property>
    <Property name='content-encryption'>A256GCM</Property>
    <Property name='payload'>{demo_payload}</Property>
    <Property name='generate-id'>true</Property>
    <Property name='expiry'>5m</Property>
    <Property name='jwks-uri'>https://jwks-service.appspot.com/.well-known/jwks.json</Property>
    <Property name='key-id'>{my_key_id}</Property>
  </Properties>
  <ClassName>com.google.apigee.edgecallouts.GenerateEncryptedJwt</ClassName>
  <ResourceURL>java://apigee-callout-encrypted-jwt-20200820.jar</ResourceURL>
</JavaCallout>

Thank you for your help. The issue is solved. The Callouts are working as expected.

@Dino-at-Google Any news when combined signing + encryption of JWT will be supported by the GenerateJWT policy?

Anyone willing to share sample proxy that first signs JWT with GenerateJWT followed by JavaCallout of JWT Encrypt callout? To avoid any stupid mistakes on my side.

@guycrets - I think probably we will not offer a single policy that produces a signed-then-encrypted output. There is a plan to support generating encrypted output with builtin policies, but that is not yet available yet. For that, currently, you need the external Java callout.

For other readers, To review, apigee has builtin policies for

  • generating signed JWT, or verifying same
  • generated encrypted JWT, or verifying same
  • generating JWS (signed arbitrary payloads), and verifying same

Apigee does not currently have builtin policies to generate or verify JWE - encrypted arbitrary content. That is what the custom Java policy cited above does.

As for an example of producing a signed JWT and then encrypting it, yes, I've updated the sample proxy in the repo for my JWE callout to show this.

What you want is a sequence of

  • GenerateJWT (builtin policy)
  • GenerateJWE (java callout)

I also recorded a brief screencast talking through this process, and showing a demonstration.

You need to take care when combining these things to use distinct keys. Take care with key management!

@Dino-at-Google

Dino, many thanks! We got this working. Unbelievable speed with which you addressed the question. Great service!

Background: external Identity Provider (Authorization Server) requires client authentication with signed+ encrypted JWT. First time we encounter this requirement.

I'm glad to be helpful, Guy. Let me know how it goes!

There were a couple twists in that example I gave.

The GenerateJWT in the example used a private key obtained from AssignMessage - essentially hardcoded into the proxy. You probably want to retrieve that from an encrypted KVM or similar.

The GenerateJwe (Java callout) used an external JWKS for getting the public key for encryption. This is a common approach for encryption, but it is not the only way to do things.

You may have to adjust these things, depending on the requirement of the authentication server.

Key management is one of the challenges when you need to perform both signing + encryption. That's what I was referring to above, when I wrote "take care with key management".

We have standard shared flows to retrieve and cache external JWKS (public signing and/or encryption keys). We never use option to have Apigee or Java code retrieve JWKS automatically for us. Further optimization is proxy triggered by external scheduler that keeps the JWKS's in cache up-to-date.

Also signed/encrypted JWT tokens that we use for client authentication towards Authorization Servers or back-ends.

For generating/rotating our own keypairs, we use external function.

Again a big thank your for your support. This is one of the distinguishing aspects of Apigee vs. competition: this Apigee community, combined with the relative ease-of-use of the policies.

@dchiesa1 Again many thanks, working fine now in production!

2 extra requirements pop up:

  1. We would like to cache the JWKS with the encryption keys. Would it be possible to pass the JWKS itself as an argument iso. the JWKS URI to the JWT encrypt?
  2. If multiple encryption keys are present, we need to pick one at random (we haven't yet developed that logic to parse the JWKS and pick an encryption key at random).

Do you think these requirements make sense? If yes, would it be an option to add them to the Java library? Else we'll make a copy and build our own version.

PS: should I better raise this as a new question?

Again many thanks for helping us out on the JWT encryption part, strongly appreciated.

Hello Guy

  1. You can specify the JWKS URI to the Java callout.   It looks like this:
    <JavaCallout name="Java-GenerateJWE-via-JWKS">
      <Properties>
        <Property name='key-encryption'>RSA-OAEP-256</Property>
        <Property name='content-encryption'>A256GCM</Property>
        <Property name='debug'>true</Property>
        <Property name='payload'>Arbitrary-string-to-encrypt,messageid={messageid},time={system.timestamp},org={organization.name},proxy={apiproxy.name}</Property>
        <Property name='jwks-uri'>https:/example.com/.well-known/jwks.json</Property>
        <Property name='key-id'>{my_key_id}</Property>
      </Properties>
      <ClassName>com.google.apigee.callouts.GenerateJwe</ClassName>
      <ResourceURL>java://apigee-callout-encrypted-jwt-20210428.jar</ResourceURL>
    </JavaCallout>
    ​
    Note the jwks-uri property.  Don't specify the public-key property; it takes precedence if it is present.
    To use this, you must provide the key-id, to allow the callout to choose one of the keys from the JWKS.  It is not possible to use any other selector. You cannot use x5c or x5c#s256, for example.  It must be a key-id.  The callout will select the key with the "kid" property as you specify.  If the key id cannot be found then, the callout will throw an exception, which results in a  runtime Fault in Apigee. 
  2. For the caching of the JWKS, this is already implemented in the callout as of March 2021. If you are using version 20210428, then you have this caching. It is hard-coded to use a TTL of 60 minutes and to retain 128 JWKS URIs. You may wish to adjust those values.
  3. Regarding selecting a key from the JWKS at random.... that is not so difficult to do , and I will be happy to add that logic. Stay tuned.
    EDIT.  Ok, I've updated the callout to select a key at random.  You need to omit the key-id property in order to tell the callout to just select a random one.  Pull the latest from the repo to get this new behavior. 

@dchiesa1 Unbelievable: wake up next morning (Europe timezone) and already implemented!

The JWKS we use contains both signing and encryption keys. To pick a random key for encryption, only encryption keys should be selected.

We would also strongly prefer to pass the whole JWKS as an argument. Multiple reasons:

  • Want to manage connection timeouts while retrieving JWKS,
  • More importantly: need to manage trusted CA certs in the callout,
  • The same JWKS is also used for signature validation in the JWT Verify policy,
  • :We want strict control over caching; it is unknow if the IdP
    • disallows encryption keys rightaway after having been removed from the JWKS or
    • starts using signing keys immediately after they have been added to the JWKS

So new wish list:

  • Filter keys from the JWKS based on the use property (encryption keys only)
  • Pass the full JWKS as an argument as opposed to the JWKS uri

Do you think these requirements make sense? If yes, would it be an option to add them to the Java library? Else we'll make a copy and build our own version.

Yes, I see.  You want to manually retrieve the JWKS on your own... and then also filter keys based on the use property.  This makes sense to me. 

Let me see....

OK, I've updated the callout do optionally read from a JWKS JSON that you provide directly. Also it filters and will select only keys with "use" = "enc", or keys with no use property. I think this should satisfy. Check the updated README for an example configuration.  

@dchiesa1 Big THANK YOU! We'll test and move forward into production.