JWE -> Direct Key working sample

Hello Team,

Any one made JWE work for Direct Key on OPDK 4.50? Seeing lot of interesting issues. Is it my mis-understanding of the usage?

Simple Generate/validate is throwing below error.. Any suggestions?

==

{"fault":{"faultstring":"No Algorithm found in JOSE Header: policy(VJ-JWT)","detail":{"errorcode":"steps.jwt.NoAlgorithmFoundInHeader"}}}

==

1.Why does there is a restriction on OutputVariable?? Can't we use any other variable example- jwt.output?

2.General question say if we use a client id for a given claim while generating how do we validate it when req comes with only JWE

/token generate jwe (with claim of client id )

/resource validate jwe (how do u validate?)

3.Side by Side Generate & validate works fine - silly

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<GenerateJWT name="GJ-Token">
	<Algorithms>
		<Key>dir</Key>
		<Content>A256CBC-HS512</Content>
	</Algorithms>
	<!--Directkey requires encoded key of the exact length required. For A256CBC-HS512, that means 64 bytes -->
	<DirectKey>
		<Value encoding="base64" ref="private.Key"/>
	</DirectKey>
	<!--<Subject ref="verifyapikey.VA-ClientId.client_id"/>-->
	<Subject ref="private.subject"/>
	<Issuer ref="private.issuer"/>
	<Audience ref="private.audience"/>
	<ExpiresIn>10m</ExpiresIn>
	<OutputVariable>output-jwt</OutputVariable>
</GenerateJWT>

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<VerifyJWT async="false" continueOnError="false" enabled="true" name="VJ-JWT">
	<DisplayName>VJ-JWT</DisplayName>
	<Source>output-jwt</Source>
	<Algorithms>
		<Key>dir</Key>
	</Algorithms>
	<DirectKey>
		<Value encoding="base64" ref="private.Key"/>
	</DirectKey>
	<Subject ref="private.subject"/>
	<Issuer ref="private.issuer"/>
	<Audience ref="private.audience"/>
</VerifyJWT>

or

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<VerifyJWT async="false" continueOnError="false" enabled="true" name="VJ-JWT">
    <DisplayName>VJ-JWT</DisplayName>
    <Source>output-jwt</Source>
    <Algorithms>
        <Key>dir</Key>
        <Content>A256CBC-HS512</Content>
    </Algorithms>
    <DirectKey>
        <Value encoding="base64" ref="private.Key"/>
    </DirectKey>
    <Subject ref="private.subject"/>
    <Issuer ref="private.issuer"/>
    <Audience ref="private.audience"/>
   
</VerifyJWT>
0 6 623
6 REPLIES 6

I'm sorry you're having trouble.

Seeing lot of interesting issues. Is it my mis-understanding of the usage?

Maybe. It works for me?

Simple Generate/validate is throwing below error.. Any suggestions?

{
    "fault": {
        "faultstring": "No Algorithm found in JOSE Header: policy(VJ-JWT)",
        "detail": {
            "errorcode": "steps.jwt.NoAlgorithmFoundInHeader"
        }
    }
}

It's not possible for me to diagnose this with the information you've given. If I saw that message, the first thing I would check is: is there an algorithm in the header of the JWT? And the way to do that is to decode it. As you know the JWT is just a dot-concatenated sequence of base6-encoded things. The header is the first piece. So to decode a JWT, just split the string by dots and then base64-decode the first part. That will retrieve the JSON of the header. Or, you can use an online tool like this one to do that work for you.

Just paste in the JWT that you provided to the VerifyJWT policy, and check to see if there is a proper header. The header should have _at least_ an alg claim and an enc claim. This is an example of what looks right.

10843-decoded-jwt.png

You'll notice that the Decoded Payload in the above image is shown as ?ciphertext? and that's because.... it's not possible to "Decode" the encrypted payload of an encrypted JWT. It's encrypted! No tool can "decode" it; the only way to recover the payload is to decrypt it, which is a cryptographic operation, which requires the cryptographic key. I haven't provided the key, and anyway that tool does not support the dir algorithm, so that decoder tool will not be able to show you the payload.

1.Why does there is a restriction on OutputVariable?? Can't we use any other variable example- jwt.output?

I don't understand the question. What restriction are you referring to? What restriction is there on the OutputVariable? There is none that I know of.

2.General question say if we use a client id for a given claim while generating how do we validate it when req comes with only JWE

The way I would do it is, encode the clientid in the payload at generation time. At Verify time, after the verify succeeds, you will have a context variable with a name like jwt.NAME_OF_VERIFY_POLICY.decoded.claim.clientid. Use a VerifyAPIKey policy passing THAT variable as the client id, to verify it.

<VerifyAPIKey name='VAPIKey-1'>
    <APIKey ref='jwt.VJ-JWT.decoded.claim.clientid'/>
</VerifyAPIKey>

3.Side by Side Generate & validate works fine - silly

I don't understand this question, sorry.

Attached please find an example API proxy that shows how you can use Apigee to generate and verify an encrypted JWT using the "dir" (DirectKey) alg. Take note! This proxy uses a fixed direct key. In a real proxy you would want a cryptographically strong encryption key if you use the dir algorithm. Example invocation:

# generate
curl -i $apigee_endpoint/ejwt-dir-demo/generate/t1
# copy output from above into a shell variable
TOKEN=eyJ0e....
# verify
curl -i $apigee_endpoint/ejwt-dir-demo/verify/t1 -d "jwt=$TOKEN"

apiproxy-ejwt-dir-demo.zip

Thankyou Dino for details.

1.PFA for the updated sample. Only change is modified to a different mechanism to send (instead of form param sending as Authorization header). Is there a spec driven to only send the req in form parm? Does it work in any other mechanism?

jwt-fail.png

2.

==

The way I would do it is, encode the clientid in the payload at generation time. At Verify time, after the verify succeeds, you will have a context variable with a name like jwt.NAME_OF_VERIFY_POLICY.decoded.claim.clientid. Use a VerifyAPIKey policy passing THAT variable as the client id, to verify it.

==

Say we encoded client id in the payload at generation time

example->

Generation time we pass below client id

<Subject ref="verifyapikey.VA-ClientId.client_id"/>

During verification only JWE would be send and no other mechanism to fetch client_id how do we get client_id for the verification to pass & decode the variables..

Any other mechanisms or we should avoid client id with in the claim since we can't get the context variable.

3. Relates to 1 when we have Generate & Validate with in same flow to just test it works with same context variable..It comes down to understanding if it is just form param only then it clarifies all kind of confusions.

jwe-rev1-2021-03-052.zip

hi Vinay

PFA for the updated sample.

I can see neither the jwt-fail.png nor the attached bundle. I don't know what happened there but I cannot see those attachments. I see hyperlinks but they don't go anywhere.

Is there a spec driven to only send the req in form parm? Does it work in any other mechanism?

The VerifyJWT can retrieve the JWT to be verified, from any context variable. In my example, I used a formparam. You can send it in a query param, but the query will be logged so that's probably a bad idea. If you pass it in as a header, that works just fine. If you use the Authorization header, you should prefix the value with "Bearer ", and omit the "Source" element in the VerifyJWT policy. As far as I know, there is no "specification" for how to send JWT into endpoints.

During verification only JWE would be send and no other mechanism to fetch client_id how do we get client_id for the verification to pass & decode the variables..

If I understand correctly, the generated (encrypted) JWT contains a claim which holds the client id. Then, the client app passes in the encrypted JWT. To retrieve the clientid, the API Proxy must call "VerifyJWT", using an appropriate decryption key, and then the clientid is available in jwt.VerifyJWT-Policy-Name.decoded.claim.clientid.

If you are sending an encrypted JWT which contains the clientid in the payload, then obviously you cannot use the clientid as the decryption key. That will not work. There is an obvious bootstrapping problem there: The secret needed to retrieve the client id is the clientid. So that won't work. Also, you cannot use anything derived from the client id in order to decrypt the JWT. This means you cannot use a Custom Attribute attached to the app or the developer, and so on. Same boot-strapping problem here.

If you lock the key in the box, then obviously you cannot unlock the box; the key is in the box.

This is true for any encryption strategy. JWT is just one example, but the principle applies regardless of the encryption approach and regardless of the algorithm. If Bob encrypts some data and he wants Alice to be able to decrypt it, Bob needs to send Alice the ciphertext, and Alice must possess the decryption key. If Bob places the decryption key inside the ciphertext, Alice cannot obtain it. Alice must independently possess the decryption key. Alice needs two pieces of information in order to decrypt: the ciphertext and the key. They must be independent things. This is true for any cryptosystem.

Making sure that Bob and Alice have an appropriate set of keys to support encryption and decryption respectively, is known as the "key management problem" in cryptography.

There are a couple ways around this, requiring you to re-think how you pass data around. To continue the analogy, either don't lock the key in the box, or don't lock the box.

  1. "don't lock the key in the box". embed the client id in the JWT header. In an encrypted JWT, the header is signed but not encrypted. Therefore it can be decoded and read, without any key. If you use this approach, then the process is: decode the JWT, obtain the decoded header claim, call VerifyAPIKey using that clientid, get a custom attribute specifying the decryption key, and then decrypt the eJWT using that decryption key.

    By inserting a reference to a key in the header of an ejwt, you can allow Bob to tell Alice which key to use. Obviously you do not wish to insert the ACTUAL key in the header; that would render the encryption useless, since any reader of the eJWT would be able to decrypt it. The point is to include a REFERENCE to a decryption key in the eJWT header, something meaningful only to Alice. Only Alice will be able to decrypt.

  2. "Don't lock the box" (eg don't encrypt the clientid). eg, use a signed JWT. A signed JWT passes the payload in cleartext, which means you do not need to verify the signature in order to read the claims. You could then follow this process: decode the JWT, retrieve the client id, call VerifyAPIKey on the clientid, retrieve a signature verification key from a custom attribute, then VerifyJWT on the signed JWT to verify all the other claims.

Any other mechanisms or we should avoid client id with in the claim since we can't get the context variable.

I don't understand what you're asking here. There is no need to avoid embedding a clientid in a claim of an encrypted JWT. Of course, whether you should embed such a claim, depends on what you're trying to accomplish.

if it is just form param only then it clarifies all kind of confusions.

I think you are asking if the GenerateJWT and VerifyJWT policies work with formparams only. If so, the answer is "No". These policies work with any context variable.

It seems to me you may have some lack of clarity on the implications of encryption, and how decryption and signing works. Read through what I wrote above, it may become clearer. You may also want to read up on encrypted JWT in general.

EDIT:

For now did a work around with service callout to send as form param since the policy doesn't support Header with Authorization Bearer<<>> format (for verify jwt with decode key).

If someone thinks other way around please guide but we need to admit if it is something missing.

For now all good as did changes to below repo for the use case (thanks a ton Dino (it always helped a lot in many ways) to support direct key for decode feature

==

https://github.com/apigee/iloveapis2015-jwt-jwe-jws/tree/master/jwe/callout

==

No action on anything at the moment.

==

Opened a case to avoid confusion Ref: 1495808

--I can see neither the jwt-fail.png nor the attached bundle. I don't know what happened there but I cannot see those attachments. I see hyperlinks but they don't go anywhere.

>>Reattached

--The VerifyJWT can retrieve the JWT to be verified, from any context variable. In my example, I used a formparam. You can send it in a query param, but the query will be logged so that's probably a bad idea. If you pass it in as a header, that works just fine. If you use the Authorization header, you should prefix the value with "Bearer ", and omit the "Source" element in the VerifyJWT policy. As far as I know, there is no "specification" for how to send JWT into endpoints.

>>Understand. Was testing via Authorization header. It doesn't work..

Attached snapshots.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ExtractVariables async="false" continueOnError="false" enabled="true" name="EV-JWEToken">
    <DisplayName>EV-JWEToken</DisplayName>
    <Source clearPayload="false">request</Source>
    <Header name="Authorization">
        <Pattern ignoreCase="true">Bearer {jwt}</Pattern>
    </Header>
</ExtractVariables>

jwe.png

--If you are sending an encrypted JWT which contains the clientid in the payload, then obviously you cannot use the clientid as the decryption key. That will not work. There is an obvious bootstrapping problem there: The secret needed to retrieve the client id is the clientid. So that won't work. Also, you cannot use anything derived from the client id in order to decrypt the JWT. This means you cannot use a Custom Attribute attached to the app or the developer, and so on. Same boot-strapping problem here.

>>this is incorrect understanding and obviously one wouldn't do that..thanks for explanation and for others who may think that way. 🙂

--I don't understand what you're asking here. There is no need to avoid embedding a clientid in a claim of an encrypted JWT. Of course, whether you should embed such a claim, depends on what you're trying to accomplish.

>>If you could do a sample with client id as a custom claim while generating and validate it which will clarify.

Do understand the basic concepts but the way the product is behaving is annoying 😞

For now did a work around with service callout to send as form param since the policy doesn't support Header with Authorization Bearer<<>> format (for verify jwt with decode key).

I don't know what you mean. What you wrote is not true: "The policy doesn't support Header with Authorization Bearer". The policy does works with the Authorization header. Maybe your configuration is not quite right.

With this configuration:

<VerifyJWT name='VerifyJWT-UsingHexEncodedKey-HeaderSource'>
  <Algorithms>
    <Key>dir</Key>
    <Content>A256CBC-HS512</Content>
  </Algorithms>
  <!--
       This configuration specifies no Source element.
       In this case, the policy retrieves the token 
       from the Authorization header implicitly,
as described in the documentation. --> <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables> <DirectKey> <Value encoding='hex' ref='private.directkey'/> </DirectKey> </VerifyJWT>

When I invoke the proxy sending the JWT as a bearer token in the customary way:

$ curl -i -H "Authorization: Bearer $JWT" \
https://$endpoint/ejwt-dir-demo/verify/t1-header -d '' -X POST

...then, the policy decrypts successfully and I can see the output payload and all the individual claims.

There is something you are doing that is not quite right. I suspect it is the variable name you have chosen to write, with ExtractVariables. What I mean is this:

<ExtractVariables async="false" continueOnError="false" enabled="true" name="EV-JWEToken">
    <DisplayName>EV-JWEToken</DisplayName>
    <Source clearPayload="false">request</Source>
    <Header name="Authorization">
        <Pattern ignoreCase="true">Bearer {jwt}</Pattern>
    </Header>
</ExtractVariables>

The JWT policy needs to write the variables "jwt.foo.bar", but if you write to the variable "jwt", then Apigee cannot write variables like "jwt.foo.bar". That's an unfortunate pitfall.

Separately, though, there is no need for you to use ExtractVariables. The VerifyJWT policy performs that extraction automatically, for you.

Wow..Good to know and will be aware of such things.

Re-tested and it works.