open-coap / java-coap

CoAP Java library
Apache License 2.0
18 stars 5 forks source link

RFC9175 Request-Tag and Echo Option #65

Closed sbernard31 closed 9 months ago

sbernard31 commented 10 months ago

I recently discover that LWM2M v1.1.1 recommends to use new CoAP Request-Tag and Echo option defined in RFC9175

The idea is mainly to solve issues described by : draft-ietf-core-attacks-on-coap.

See more details at :

AFAIK, this is not implemented by java-coap, right ? Is it something that is planned ?

szysas commented 10 months ago

Hi, Thanks for pointing this out. Yes, I think this should be implemented. Echo one is simple on client but on server there is lot to of options Request-Tag seems to be otherwise. How much of it do you think should be done in this library and what in Leshan?

sbernard31 commented 10 months ago

My current understanding is there is no specific usage of those 2 options for LWM2M . There are just recommended since LWM2M v1.1.x. (before they was not mentioned at all).

So as this is mainly CoAP features, I think ideally most should be part of the CoAP Library.

As this is mainly about hardening CoAP, I think ideally, most should be activated by default and works transparently for a CoAP users. Then if for some reasons a CoAP user want to optimize traffic, he should understand the consequence and see if he can remove/tweak some of those features.
But I'm sure this goal :point_up: is achievable.

You could find some of my understanding/questioning about that at :

Here is a description of what is done in libcoap. : https://github.com/obgm/libcoap/pull/786 @mrdeep1, if you have any advice about that OR if you want to discuss with us about what behavior should be implemented, your knowledge is welcomed. :pray:

(Note that I understand the echo option is strongly recommended (mandatory?) when using OSCORE.)

mrdeep1 commented 10 months ago

Echo Option

(Note that I understand the echo option is strongly recommended (mandatory?) when using OSCORE.)

Yes, mandatory, if RFC8613 Appendix B.1.2 is to be supported, of if freshness checks are required.

The CoAP client stack always needs to be able to respond to any Echo option sent by the server (the client app should not need to see this interchange).

The server is more complicated. For OSCORE (in implementing Appendix B.1.2) this needs to be done by the OSCORE layer on the server stack. Otherwise it is down to a higher level server application to decide when it needs to check "freshness" by adding in an Echo Option in the response (ignoring the initial request) and then checking the validity in a subsequent request that contains the Echo option.

Something that libcoap server CoAP stack currently does not do is to send an Echo option for each new client "connection" to check validity freshness of the client, as this was thought likely to break a lot of clients that dont understand Echo.

Request Tag

Request-Tag Option does need to be used whenever there is any Block2 type response to make sure that the server is returning the correct response data for the client's next Block2 transfer request.

Likewise, it does need to be used when there is any concurrent Block1 transfers (e.g. concurrent FETCH to same URL, but different specific requests in the payload). This is the same logic as using Etag Option in the response, but using Request-Tag Option in the request.

When there is a request that does not need Block1, but triggers a response that needs to use Block2, the request may not have the Request-Tag Option and if not, then there could be confusion in in making sure the server sends back the correct Block2.

For libcoap, I took the decision that by default, Request-Tag is sent with every request (even if Block1 is not being used and Block2 was not defined) "just in case" there is a Block2 sized response. The client application can however disable the CoAP stack doing this, only sending (done by CoAP stack) Request-Tag if Block1 or Block2 were defined. So far no complaints.......

sbernard31 commented 10 months ago

@mrdeep1 thx a lot for the explanation.

Otherwise it is down to a higher level server application to decide when it needs to check "freshness" by adding in an Echo Option in the response (ignoring the initial request) and then checking the validity in a subsequent request that contains the Echo option.

I have some concerns about this because in my experience application developers have no deep knowledge of protocol they are using (and it's understandable), so relying on them sounds not so good. I think most will consider there are safe if they are using DTLS. (like when you're using TLS) and so Echo option will not be largely used.

Could we imagine a default safe behavior ? Maybe have a default freshness value for all resources (I don't know what time would be suitable ?) Users can change this default value OR deactivate it but in this case, they will be expose to documentation about echo option. (and hopefully will read it :sweat_smile: ) Eventually they could also change(or deactivate) freshness by resource ?

Something that libcoap server CoAP stack currently does not do is to send an Echo option for each new client "connection" to check validity freshness of the client, as this was thought likely to break a lot of clients that dont understand Echo.

What do you mean by "each new client connection" ? This makes me think to new DTLS connection but new connection need new handshake and I was thinking that an handshake was a kind of freshness proof ?

mrdeep1 commented 10 months ago

Could we imagine a default safe behavior ?

I cant think of a safe default option to send Echo for whatever reason (other than to an OSCORE enabled client) as the client may not know how to handle the Echo option and then hence game over.

However if by default it is enabled for a server, there needs to be a way of disabling it, or having an exception client list which the app programmer will need to find to make things work for any non Echo compliant clients.

What do you mean by "each new client connection" ?

I was thinking about the CoAP layer, not anything underneath. Yes, (D)TLS or TCP new session may indicate freshness, but not the freshness of a CoAP request.

sbernard31 commented 9 months ago

I cant think of a safe default option to send Echo for whatever reason as the client may not know how to handle the Echo option and then hence game over.

Sometime this kind of breakage is necessary to spread a feature in the ecosystem. There is some example for (D)TLS, some library decide to reject by default client which doesn't support Renegotiation Indication Extension.

Maybe too soon to do that for Echo option but this could be the right move at some time ?

having an exception client list which the app programmer will need to find to make things work for any non Echo compliant clients.

Maybe this is something which could make sense at LWM2M level as there is a clear defined way to identify a client :thinking:

What do you mean by "each new client connection" ?

I was thinking about the CoAP layer, not anything underneath.

So what you call new connection is : a new CoAP message after long time without communicate ?

Yes, (D)TLS or TCP new session may indicate freshness, but not the freshness of a CoAP request.

Just to be sure, just after an DTLS handshake, there is no way to push old intercepted request, so we can consider the request "fresh" enough, right ?

mrdeep1 commented 9 months ago

Could we imagine a default safe behavior ?

A way to go here is to have a server app callable function force_refresh() which then tells the CoAP layer that any new request(s) that come in are to have a4.01 Unauthorized + Echo Option sent back until a new request comes in with the correct Echo option (which clears 'force_refresh') and the new request (and subsequent requests then get passed to the app.

So, the server app does not need to know how to do the Echo heavy lifting, but has control over when it is done.

mrdeep1 commented 9 months ago

Maybe too soon to do that for Echo option but this could be the right move at some time ?

Certainly too soon.

So what you call new connection is : a new CoAP message after long time without communicate ?

Yes.

Just to be sure, just after an DTLS handshake, there is no way to push old intercepted request, so we can consider the request "fresh" enough, right ?

DTLS session setup establishes "trust", which can be deemed "fresh", but then the CoAP layer (as in OSCORE RFC8162 Appendix B.1.2) needs to make sure that the Sender Sequence Number is fresh. So, it comes down to what is required to be fresh.

sbernard31 commented 9 months ago

@mrdeep1 thx for those inputs. Very useful :pray:

szysas commented 9 months ago

@mrdeep1 Thanks a lot. I think we will follow your approach with echo and request-tag. The only thing is whether and how to make it mandatory by default. I tend to agree with @sbernard31 that it should be somehow defaulted otherwise users will not do it as it does not bring and new features.

sbernard31 commented 9 months ago

I guess this can be done step by step.

  1. Support option just to be able to reply to peer which asks for it. So echo for client and request-tag for server.
  2. Add a way to manually activate echo at server side for a specific resource and request-tag at client side for specific request (I guess this will be at least useful for unit tests ?)
  3. Add automatic support for request-tag (probably activated by default ?), I think we have a pretty clear idea about how it should looks like. :point_up:
  4. For a more advance way/API for echo at server side (and default activation ?), there is some ideas but probably need more thinking/discussion to find the right way.

@mrdeep1 if you get some more information idea about this in the future. Do not hesitate to share here :pray:

szysas commented 9 months ago

@mrdeep1 In libcoap are you caching 'echo' value per resource (uri-path) or globally (for any resource)?

mrdeep1 commented 9 months ago

In libcoap are you caching 'echo' value per resource (uri-path) or globally (for any resource)?

Nothing is cached for the client, other than the original request which needs to be re-transmitted with Echo option added.

For the server, I cache the Echo value in OSCORE (recipient) logic for the repeated request as that is the only place I currently generate Echo in the libcoap stack.

If I implement the force_refresh() logic (likely), then I would be caching the Echo value (which could be several if the client sends a burst of requests before seeing the first 4.01 + Echo response) in what libcoap calls the coap_session_t structure (which defines the "connection" between 2 CoAP Endpoints).

As 2 clients could initiate a request to the same resource (uri-path) at the same time, I think it dangerous to use a common Echo value for both clients, and so that would be the wrong place to do any caching (and similarly for global).

It is unclear to me as to whether you are thinking of sending back an Echo every time the server gets a new request, and then allows the repeat request to go up the stack with Echo option, or whether you are thinking that a client is deemed to be "fresh" and then stopping sending back the Echo for each subsequent request. The former will double the packets sent across the network.

szysas commented 9 months ago

I'm looking now at client side if it would be possible to avoid (or minimise) doubling requests, without and then with echo. Caching last received echo and always using it for new requests. So yes, it would require that server always send response with echo, whenever freshness is required. Probably that would need to be done per resource (uri-path).

sbernard31 commented 9 months ago

For echo option, at client side I see several possible mode :

  1. [On error], only use Echo Option on repeated request when server answer with 4.01 (Unauthorized) response with an Echo option.
  2. [By resource by client] As soon as a client get a Echo Option in a response for a given resource, he reuse it for all operation on this resource.
  3. [By client], as soon as he get a Echo Option, client reuse it for all request sent to this client.

About 1. is the minimal one for interoperability but suboptimal if freshness is often required. (maybe a good first step?) About 2. I'm not sure if this is useful ? maybe for use case where there is only few resource which required freshness. About 3. I understand this is compliant with the RFC ? for maximum freshness :snowflake: (default mode ?)

As 2 clients could initiate a request to the same resource (uri-path) at the same time, I think it dangerous to use a common Echo value for both clients, and so that would be the wrong place to do any caching (and similarly for global).

The RFC seems to allow it. "The server MAY include the same Echo option value in several different response messages and to different clients"

mrdeep1 commented 9 months ago

"The server MAY include the same Echo option value in several different response messages and to different clients"

Agreed. Mea Culpa - I was thinking earlier I needed to go back and re-read RFC9175 carefully....now I find I do. Sorry if I have confused things.

A lot of this depends on what the server policy is for requiring Echo options to be used, and what are they associated with.

So, if the server policy is defining freshness in terms of time freshness, then the client needs to cache the current Echo value and use it in every request until the server sends out an updated Echo option value. It is unclear to me what happens if a client tracks Echo values associated with different resource requests as to how the server would handle this, but that again is down to some sort of OOB understanding of what the "Echo" policy is at both ends. So, I am inclined to go with 3. for this one.

So, we are moving here towards if server sends an echo for whatever reason, then (as we don't know it is a time based freshness or not) then all subsequent requests need to include the latest Echo option value as sent by the server.

However, if we are implementing RFC8613 Appendix B.1.2, then the Echo option should only be used for that interchange unless the server elects to also send another Echo for whatever reason at some later point. Relatively easy to handle this corner case.

And then what does the client do if the server reboots and issues Echo to all the now (re-joining) clients as attack amplification mitigation (or has attack mitigation in place for any new joining client). Does the client just continue to send the same Echo in every request, hence consuming some of the bandwidth? How does the client know that this is not a time based freshness Echo from the server?

sbernard31 commented 9 months ago

Agreed. Mea Culpa - I was thinking earlier I needed to go back and re-read RFC9175 carefully....now I find I do. Sorry if I have confused things.

No problem, understanding specification is often not so easy. I like we try to do that together.

And then what does the client do if the server reboots and issues Echo to all the now (re-joining) clients as attack amplification mitigation (or has attack mitigation in place for any new joining client). Does the client just continue to send the same Echo in every request, hence consuming some of the bandwidth? How does the client know that this is not a time based freshness Echo from the server?

I guess you are talking about 3. point from RFC9175Β§2.4. Applications of the Echo Option ? I understand this is mainly for coap without any security, as it is said : " server that provides a large amplification factor to an unauthenticated peer SHOULD mitigate amplification attacks ..."

So this is not for coap over OSCORE or DTLS, but just for plain CoAP I would said this is less important/common use case. Too bad if by default a client send too many Echo option, maybe this is the better trade-off.

Unless we try to change the RFC to allow server to say to client if they want the Echo Option "repeat once" or "only for this resource" or "for all resources". 🀷 (There is also some questioning on requrest-tag with block2 maybe this RFC need to be enhanced ?)

szysas commented 9 months ago

It looks like server echo generating strategy can be anything (time based, event based or.. ) so client can not assume anything as well. That's why I'm not sure about going forward with point 3. With point 2, the downside is that client needs to cache as many echo's as many actuator resources there are (I'd exclude all GET and FETCH requests).

sbernard31 commented 9 months ago

With point 2, the downside is that client needs to cache as many echo's as many actuator resources there are (I'd exclude all GET and FETCH requests).

Yep could be more painful to implement so maybe not a good option (at least for first implementation) ...

It looks like server echo generating strategy can be anything (time based, event based or.. ) so client can not assume anything as well. That's why I'm not sure about going forward with point 3.

I get the point, but if client doesn't know how echo is used at server side (which I guess it should be pretty common), I"m not sure we will find a solution without drawback.

So, maybe [1. on error] could be a good first step.

And [3. by client] sounds to be the better choice for default behavior if echo option should be massively used to prevent :

Maybe we should ask more about that :

(we maybe missed something OR there is maybe something that authors should be aware of?)

szysas commented 9 months ago

Going back to Request-Tag.

For libcoap, I took the decision that by default, Request-Tag is sent with every request (even if Block1 is not being used and Block2 was not defined) "just in case" there is a Block2 sized response.

I'm probably missing something from RFC9175, but setting Request-Tag only make sense in request with a payload. In practice POST,PUT,FETCH. We could got further and assume to place it only in larger payload when there is a chance that server will try to split it.

mrdeep1 commented 9 months ago

I'm probably missing something from RFC9175..

The issue here is, say, sending a request with minimal or 0 data (e.g. GET) which triggers a response from the server that requires the use of Block2 to transfer all of the data. If there is a cast iron guarantee that this (Block2 response) will never happen, or there will never be concurrent requests, then there is nothing to worry about (assuming ETags are properly being used).

Use of FETCH with different parameters in the payload (even if they fit into a single packet) to the same resource will get back different responses. If there are never concurrent requests - still fine.

However, if there is any possibility of concurrent requests and a response comes back using Block2 there is a potential data corruption issue unless Request-Tag is used (which effectively updates the cache-key matching) in addition, to make sure that the correct data is returned.

Request-Tag should be in place if Block1 or Block2 (NUM = 0) options are present in a request, no matter what. Otherwise, we currently have to choose to include it in every request that is sent, or re-request if the response contains a Block2 and client did not send a Request-Tag.

I think DELETE is the only case where Request-Tag should not be used in a request.

sbernard31 commented 9 months ago

In practice POST,PUT,FETCH. We could got further and assume to place it only in larger payload when there is a chance that server will try to split it.

For block1 this is true, but for block1 client should know if it will use blockwise or not so it's easy to put request-tag only when this is strictly needed.

I'm probably missing something from RFC9175, but setting Request-Tag only make sense in request with a payload.

I guess the missing point is about block2. block2 can be used for GET too. and you don't know before if server will split the response in block2. So that's why the simple solution could be to always put a request-tag.

Another solution is proposed at the end of https://github.com/eclipse-californium/californium/pull/2088#issuecomment-1710127219 :

Otherwise, if you send a normal GET and the server splits it into multiple blocks and sends BLOCK N=0, then what do you do for the next block query? Do you generate a Request-Tag or not? If you generate a Request-Tag, this is actually a new transfer and you need to start block block N=0.

But that means you need to resend the GET request from beginning to add the Request-Tag. (maybe it's OK ?)

Another other solution, which need more (too?) logic/state at client side. For block2 :

szysas commented 9 months ago

Thanks, I was under impression that Request-tag protects requests payloads (Block1) and Etag protects response payloads (Block2). https://www.rfc-editor.org/rfc/rfc9175#section-3.8

Is it so that client does not trust server use of Etag and that's why we should use Request-tag also with GET requests?

szysas commented 9 months ago

To prevent draft-ietf-core-attacks-on-coap§2.4.The Request Fragment Rearrangement Attack, I'm not sure request-tag is needed for block2, I understand this is only about block1 but maybe missed something ?

That's exactly my assumption. (BTW, I'm not looking at concurrent block transfers for same resource.)

Looking at this simple example, how would request-tag helped:

     β”Œβ”€β”€β”€β”€β”€β”€β”                             β”Œβ”€β”€β”€β”€β”€β”€β”
     β”‚Clientβ”‚                             β”‚Serverβ”‚
     β””β”€β”€β”¬β”€β”€β”€β”˜                             β””β”€β”€β”¬β”€β”€β”€β”˜
        β”‚            𝟏 GET /large            β”Œβ”΄β”  
        β”‚ ──────────────────────────────────>β”‚ β”‚  
        β”‚                                    β””β”¬β”˜  
        β”‚ 𝟐 2.05 block2(0:512:more) etag(7e) β”‚    
        β”‚ <─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─    
        β”‚                                    β”‚    
        β”‚  πŸ‘ GET /large block2(1:512:more)   β”Œβ”΄β”  
        β”‚ ──────────────────────────────────>β”‚ β”‚  
        β”‚                                    β””β”¬β”˜  
        β”‚ πŸ’ 2.05 block2(1:512:last) etag(7e) β”‚    
        β”‚ <─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─    
     β”Œβ”€β”€β”΄β”€β”€β”€β”                             β”Œβ”€β”€β”΄β”€β”€β”€β”
     β”‚Clientβ”‚                             β”‚Serverβ”‚
     β””β”€β”€β”€β”€β”€β”€β”˜                             β””β”€β”€β”€β”€β”€β”€β”˜
mrdeep1 commented 9 months ago

Looking at this simple example, how would request-tag helped:

Not needed as there are no concurrent requests. Request-Tag needed in addition if are multiple (which are different) responses (split over respective Block2s). More likely an issue with FETCH rather than GET.

szysas commented 9 months ago

Thanks.

To summarise, do you agree that setting request-tag option for any request with non empty payload, will prevent draft-ietf-core-attacks-on-coap§2.4.The Request Fragment Rearrangement Attack:

mrdeep1 commented 9 months ago

I agree that Request-Tag inclusion for Block1 transfers fixes the described Fragment Rearrangement Attack (assuming theserver supports Request-Tag).

See Coap Attacks re Request-Tags which may help your thinking here about handling Block2 type responses.

szysas commented 9 months ago

Closing as minimal support was added.