Subject: [TLS] Binding imported PSKs to KDFs rather than hash functions

From: "Christopher Wood" <[email-masked]>
Date: Sat, 14 Sep 2019 07:53:17 -0700

Hi folks,

Martin reviewed the external PSK draft [1] and filed a couple issues [2,3] that are worth discussion here. I’d like to call attention to #16 [3] in particular. 

TL;DR: Should we bind the importer to a KDF rather than a hash function?

Currently, the importer draft assumes that an external PSK is optionally associated with some hash function, and, if not, assumes SHA256. The importer uses this hash function when deriving the imported key. In particular, it’s the hash function used for HKDF-Extract and HKDF-Expand, which runs over the external PSK blob and the constructed ImportedIdentity to produce a unique, imported PSK. ImportedIdentity is the structure that contains the external PSK identity, a protocol label, and target hash function:

~~~
struct {
  opaque external_identity<1...2^16-1>;
  opaque label<0..2^8-1>;
  HashAlgorithm hash;
} ImportedIdentity;
~~~

The issue raised pointed out that the (protocol label, hash) tuple effectively identifies a KDF. (Martin, please correct me if I’m misinterpreting your comments!) Thus, perhaps it makes sense to replace this with something simpler, e.g., 

~~~
struct {
  opaque external_identity<1...2^16-1>;
  uint16 protocol_version;
  uint16 kdf_identifier;
} ImportedIdentity;
~~~

Where ImportedIdentity.protocol_version is a code point (e.g., 0x0304 for TLS 1.3) and ImportedIdentity.kdf_identifier is a unique (registry-defined) KDF function (e.g., 0x0001 for HKDF-SHA256). If we did this, a couple interesting questions arise. 

First, should we keep using HKDF as the importer KDF? Note that the current ImportedIdentity.hash field is what diversifies imported PSKs by hash function. The actual key derivation still uses HKDF with the hash function associated with the external PSK. (SHA-256 is assumed if none is specified.) If we replaced ImportedIdentity.hash with ImportedIdentity.kdf_identifier, we would still get this diversification, since each target KDF is bound to one hash function. However, should the KDF corresponding to ImportedIdentity.kdf_identifier be the KDF used by the importer for derivation? That is, let’s say I have an external PSK "epsk" that's associated with SHA-512. Moreover, let's assume ImportedIdentity.kdf_identifier = 0xFEFE, which corresponds to some KDF called MySpecialKDF. Should the derivation step be this?

~~~
epskx = HKDF-SHA512-Extract(0, epsk)
ipskx = HKDFSHA512-Expand-Label(epskx, "derived psk", Hash(ImportedIdentity), Hash.length)
~~~

Or this?

~~~
epskx = MySpecialKDF-Extract(0, epsk)
ipskx = MySpecialKDF-Label(epskx, "derived psk", MySpecialKDF.Hash(ImportedIdentity), MySpecialKDF.length)
~~~

Or something else entirely?

Second, keys need to be imported for each supported ciphersuite and corresponding hash function. How would we specify that if we now speak of importers in terms of a target KDF?

Thanks,
Chris (no hat)

[1] https://github.com/tlswg/draft-ietf-tls-external-psk-importer
[2] https://github.com/tlswg/draft-ietf-tls-external-psk-importer/issues/15
[3] https://github.com/tlswg/draft-ietf-tls-external-psk-importer/issues/16

---

From: "Martin Thomson" <[email-masked]>
Date: Tue, 17 Sep 2019 11:26:18 +1000

There are two points here to consider:

1. Whether the key that we are feeding into this process is going to be used exclusively for that purpose, or whether it might be used for something else.

2. How the key that is output from this process might need diversification.

What we learned from TLS 1.3 is that HKDF is effectively a completely different KDF when it is used with a different hash function.  And that taking the same key into two different functions was not advisable because there isn't a lot of analysis to support that particular usage pattern.

In thinking about the first point, we might want to consider whether the KDF that is used in the importer might need to be used in other ways.  Personally, when I see the question formulated this way, I am inclined to suggest that this key should be used exclusively for key import.  If that's the decision, that should be made very clear.

If we decide that we're gaining exclusive access, then the only other consideration I'm aware of on the input side is the binding to the origination of the key, as raised by Jonathan.  That is something we might gain by integrating something into the identity, or by adding a context attribute to the expansion (which might carry a session hash).  That concern is not reflected in the proposal here though.

If you have exclusive access to the input keying material, then you can very easily restrict this to HKDF.  If not, then I'd suggest that you need to use the more general form.  But then you also need to ensure that whatever inputs you feed into the KDF can't collide with inputs for the other usages.  That's tricky.

Then the question is how we identify the diversification parameters.  Right now, we want to diversify outputs based on TLS version and the hash algorithm associated with the cipher suite.  If this is only ever used for TLS, then we have a simple process: we identify the specific type of TLS PRF somehow and bake that into the KDF label.

How we do that is simple mechanics: each version of TLS we care about has a template KDF, which is parameterized by hash function.  As Chris proposes, we could include a TLS version and a hash function identifier.  That seems enough.

The final question is about multiple PRFs in TLS 1.2.  We never restricted input PSKs in TLS 1.2 to a single KDF, so in theory we could use a single output of this importer function for TLS 1.2.  However, I'm not sure that this is necessary if we allow for the possibility of an importer.  We could use different keys depending on the final selection of cipher suite (and therefore hash function).  I don't see any reason not to fix TLS 1.2 PSK usage at the same time as TLS 1.3.

On Sun, Sep 15, 2019, at 00:53, Christopher Wood wrote:
> Hi folks,
> 
> Martin reviewed the external PSK draft [1] and filed a couple issues 
> [2,3] that are worth discussion here. I’d like to call attention to #16 
> [3] in particular. 
> 
> Currently, the importer draft assumes that an external PSK is 
> optionally associated with some hash function, and, if not, assumes 
> SHA256. The importer uses this hash function when deriving the imported 
> key. In particular, it’s the hash function used for HKDF-Extract and 
> HKDF-Expand, which runs over the external PSK blob and the constructed 
> ImportedIdentity to produce a unique, imported PSK. ImportedIdentity is 
> the structure that contains the external PSK identity, a protocol 
> label, and target hash function:
> 
> ~~~
> struct {
> The issue raised pointed out that the (protocol label, hash) tuple 
> effectively identifies a KDF. (Martin, please correct me if I’m 
> misinterpreting your comments!) Thus, perhaps it makes sense to replace 
> this with something simpler, e.g., 
> 
> ~~~
> struct {
> Where ImportedIdentity.protocol_version is a code point (e.g., 0x0304 
> for TLS 1.3) and ImportedIdentity.kdf_identifier is a unique 
> (registry-defined) KDF function (e.g., 0x0001 for HKDF-SHA256). If we 
> did this, a couple interesting questions arise. 
> 
> First, should we keep using HKDF as the importer KDF? Note that the 
> current ImportedIdentity.hash field is what diversifies imported PSKs 
> by hash function. The actual key derivation still uses HKDF with the 
> hash function associated with the external PSK. (SHA-256 is assumed if 
> none is specified.) If we replaced ImportedIdentity.hash with 
> ImportedIdentity.kdf_identifier, we would still get this 
> diversification, since each target KDF is bound to one hash function. 
> However, should the KDF corresponding to 
> ImportedIdentity.kdf_identifier be the KDF used by the importer for 
> derivation? That is, let’s say I have an external PSK "epsk" that's 
> associated with SHA-512. Moreover, let's assume 
> ImportedIdentity.kdf_identifier = 0xFEFE, which corresponds to some KDF 
> called MySpecialKDF. Should the derivation step be this?
> 
> ~~~
> ipskx = MySpecialKDF-Label(epskx, "derived psk", 
> MySpecialKDF.Hash(ImportedIdentity), MySpecialKDF.length)
> ~~~
> 
> Second, keys need to be imported for each supported ciphersuite and 
> corresponding hash function. How would we specify that if we now speak 
> of importers in terms of a target KDF?
> 
> Thanks,
> Chris (no hat)
> 
> _______________________________________________
> TLS mailing list
> [email-masked]
> https://www.ietf.org/mailman/listinfo/tls
>

---

From: "Martin Thomson" <[email-masked]>
Date: Tue, 17 Sep 2019 12:18:58 +1000

On Tue, Sep 17, 2019, at 11:26, Martin Thomson wrote:
> What we learned from TLS 1.3 is that HKDF is effectively a completely 
> different KDF when it is used with a different hash function.

Hugo points out that I should clarify this to add:

One should not use HKDF with two hash functions (or more generally two KDF functions) and the same IKM. The same way as you should not use two different cryptographic functions with the same key.

This is an idea that this draft exists to support, so it needs to be very careful about how it does the same itself.

---

From: "Christopher Wood" <[email-masked]>
Date: Tue, 17 Sep 2019 07:56:26 -0700

Hi Martin,

Thanks for the reply! Please see inline below.

On Mon, Sep 16, 2019, at 6:26 PM, Martin Thomson wrote:
> 1. Whether the key that we are feeding into this process is going to be 
> used exclusively for that purpose, or whether it might be used for 
> something else.
> 
> different KDF when it is used with a different hash function.  And that 
> taking the same key into two different functions was not advisable 
> because there isn't a lot of analysis to support that particular usage 
> pattern.
> 
> In thinking about the first point, we might want to consider whether 
> the KDF that is used in the importer might need to be used in other 
> ways.  

To be clear, you're referring to HKDF and its role in deriving ipsk from epsk, right?

> Personally, when I see the question formulated this way, I am 
> inclined to suggest that this key should be used exclusively for key 
> import.  If that's the decision, that should be made very clear.

I agree. We can state something along the lines of "clients MUST NOT use an external TLS PSK for any other purpose." And also say that external PSKs can only be associated with a single hash function. As you mention above and in your followup message, if this isn't done, then a client may import the same IKM using different hash functions. 

Is this what you had in mind?

> If we decide that we're gaining exclusive access, then the only other 
> consideration I'm aware of on the input side is the binding to the 
> origination of the key, as raised by Jonathan.  That is something we 
> might gain by integrating something into the identity, or by adding a 
> context attribute to the expansion (which might carry a session hash).  
> That concern is not reflected in the proposal here though.
>
> If you have exclusive access to the input keying material, then you can 
> very easily restrict this to HKDF.  If not, then I'd suggest that you 
> need to use the more general form.  But then you also need to ensure 
> that whatever inputs you feed into the KDF can't collide with inputs 
> for the other usages.  That's tricky.

Right. Tricky and likely impossible. There's nothing stopping someone from crafting a PSK with an identity which matches an ImportedIdentity, and then advertising that on the wire.

> Then the question is how we identify the diversification parameters.  
> Right now, we want to diversify outputs based on TLS version and the 
> hash algorithm associated with the cipher suite.  If this is only ever 
> used for TLS, then we have a simple process: we identify the specific 
> type of TLS PRF somehow and bake that into the KDF label.
>
> How we do that is simple mechanics: each version of TLS we care about 
> has a template KDF, which is parameterized by hash function.  As Chris 
> proposes, we could include a TLS version and a hash function 
> identifier.  That seems enough.

I suspect you meant "KDF identifier" here? (The proposal below uses the TLS version and a KDF identifier. It could also use the TLS version and a HashAlgorithm value. Using a KDF identifier is probably more future proof, though.)

> The final question is about multiple PRFs in TLS 1.2.  We never 
> restricted input PSKs in TLS 1.2 to a single KDF, so in theory we could 
> use a single output of this importer function for TLS 1.2.  However, 
> I'm not sure that this is necessary if we allow for the possibility of 
> an importer.  We could use different keys depending on the final 
> selection of cipher suite (and therefore hash function).  I don't see 
> any reason not to fix TLS 1.2 PSK usage at the same time as TLS 1.3.

Sure, it's possible, though I think that's already done? Was there something below or in the draft that made you think otherwise?

Thanks again!
Chris

---

From: "Martin Thomson" <[email-masked]>
Date: Wed, 18 Sep 2019 12:03:26 +1000

> To be clear, you're referring to HKDF and its role in deriving ipsk 

Right.  Though I see epsk -> ipskx in the draft.  Not sure why that has an 'x'.

> I agree. We can state something along the lines of "clients MUST NOT 
> use an external TLS PSK for any other purpose." And also say that 
> external PSKs can only be associated with a single hash function. As 
> you mention above and in your followup message, if this isn't done, 
> then a client may import the same IKM using different hash functions. 
> 

Yeah.  `epsk` can only be used for importation into TLS using this mechanism and only with a single hash function.

> Right. Tricky and likely impossible. There's nothing stopping someone 
> from crafting a PSK with an identity which matches an ImportedIdentity, 
> and then advertising that on the wire.

We can't stop all the bad ideas :)

One thing that bothers me a little: how did you plan to identify these?  I imagine that you would only need to identify the epsk in the TLS handshake.  You don't plan to use some derived means of identification, do you?  The doc is light on details here and could probably use some text on this point.
 
> I suspect you meant "KDF identifier" here? (The proposal below uses the 
> TLS version and a KDF identifier. It could also use the TLS version and 
> a HashAlgorithm value. Using a KDF identifier is probably more future 
> proof, though.)

Yeah, a KDF identifier is more robust, for sure.  It is probably not coincidence that version+hash is enough for now, but we can be a tad more general in the interests of making this more robust.
 
> Sure, it's possible, though I think that's already done? Was there 
> something below or in the draft that made you think otherwise?

Probably just the discussions on GitHub and the lack of discrete identifiers for the TLS 1.2 KDFs in the IANA section.  If we're identifying KDFs, then we should identify TLS 1.2 KDF separately from the TLS 1.3 ones.

---

From: "Christopher Wood" <[email-masked]>
Date: Wed, 18 Sep 2019 08:41:23 -0700

> Right.  Though I see epsk -> ipskx in the draft.  Not sure why that has an 'x'.

It was an editorial thing meant to help disambiguate keys. We can probably clean this up.

> Yeah.  `epsk` can only be used for importation into TLS using this 
> mechanism and only with a single hash function.

Great! I'll draft a PR with this restriction that builds on https://github.com/tlswg/draft-ietf-tls-external-psk-importer/pull/18.

> One thing that bothers me a little: how did you plan to identify these? 
>  I imagine that you would only need to identify the epsk in the TLS 
> handshake.  You don't plan to use some derived means of identification, 
> do you?  The doc is light on details here and could probably use some 
> text on this point.

Assuming you're referencing imported keys by "these", the identity sent on the wire is the constructed ImportedIdentity structure. I'll try to make that more clear.

> Yeah, a KDF identifier is more robust, for sure.  It is probably not 
> coincidence that version+hash is enough for now, but we can be a tad 
> more general in the interests of making this more robust.

Agreed. A downside here is that we're then forced into a registry for the KDFs. I'd prefer referencing something like HPKE for that. 

> Probably just the discussions on GitHub and the lack of discrete 
> identifiers for the TLS 1.2 KDFs in the IANA section.  If we're 
> identifying KDFs, then we should identify TLS 1.2 KDF separately from 
> the TLS 1.3 ones.

Ah, so, I think this is where the miscommunication is happening! The target KDFs I've been envisioning are not protocol specific. So, we wouldn't use the TLS 1.2 PRF. We'd just use the identifier for HKDF-SHA256, or whatever. (Since the hash algorithm is what's important, and is effectively what identifies the TLS 1.2 PRF, I don't see the need to specifically reference the 1.2 PRF. It seems we can just target a hash-specific KDF.) 

In an attempt to clarify my mental model, here's a proposal of I mean. First, define ImportedIdentity as follows:

~~~
   struct {
       opaque external_identity<1...2^16-1>;
       opaque context<0..2^16-1>;
       uint16 protocol;
       uint16 kdf;
   } ImportedIdentity;
~~~

And then define the KDF registry (here or elsewhere) as:

~~~
| KDF Name.        | KDF ID |
----------------------------
| HKDF-SHA256 |  0x0001  |
| HKDF-SHA384 |  0x0002  |
| ...                          |  ...             | // maybe add one for the MD5+SHA1 PRF
~~~

Then, when importing one PSK for TLS 1.2 and 1.3, targeting SHA256, we'd import two keys: one with ImportedIdentity.protocol = 0x0303 and ImportedIdentity.kdf = 0x0001, and another with the same KDF identifier but ImportedIdentity.protocol = 0x0304. 

If I'm still missing your point (or if the preceding proposal is simply wrong!), can you update the text above or propose new text to clarify?

Thanks again for the continued discussion.

Best,
Chris

---

From: "Martin Thomson" <[email-masked]>
Date: Thu, 19 Sep 2019 09:31:51 +1000

On Thu, Sep 19, 2019, at 01:41, Christopher Wood wrote:
> Ah, so, I think this is where the miscommunication is happening! The 
> target KDFs I've been envisioning are not protocol specific. 

As HKDF and the TLS 1.2 PRF are not the same function, wouldn't it be better to have separate identifiers?  Sure, we could rely on the `protocol` field to diversify the output, but I think that we should be applying the same principle throughout, namely that the one key is only used with the one KDF instantiation.

---

From: "Christopher Wood" <[email-masked]>
Date: Thu, 19 Sep 2019 06:41:12 -0700

On Wed, Sep 18, 2019, at 4:31 PM, Martin Thomson wrote:
> As HKDF and the TLS 1.2 PRF are not the same function, wouldn't it be 
> better to have separate identifiers?  Sure, we could rely on the 
> `protocol` field to diversify the output, but I think that we should be 
> applying the same principle throughout, namely that the one key is only 
> used with the one KDF instantiation.

Agreed on the principle, especially if future versions of TLS define new KDFs and we want to avoid using the same imported key across both.

I'll make that change after #18 (https://github.com/tlswg/draft-ietf-tls-external-psk-importer/pull/18) lands, at which point we should be able to close issues #15 and #16. 

Best,
Chris

---

From: "Christopher Wood" <[email-masked]>
Date: Fri, 27 Sep 2019 17:30:39 -0700

On Thu, Sep 19, 2019, at 6:41 AM, Christopher Wood wrote:
> Agreed on the principle, especially if future versions of TLS define 
> new KDFs and we want to avoid using the same imported key across both.
> 
> I'll make that change after #18 
> (https://github.com/tlswg/draft-ietf-tls-external-psk-importer/pull/18) 
> lands, at which point we should be able to close issues #15 and #16. 

OK, #18, modified to take this discussion into account, has been merged. Thanks to everyone who commented here and on GitHub!

Best,
Chris