Change:
3. <del> Intuitively, key importers mirror the concept of key exporters in TLS in that they diversify a key based on some contextual information before use in a connection.  In contrast to key exporters, wherein differentiation is done via an explicit label and context string, the key importer defined herein uses a label and set of hash algorithms to differentiate an external PSK into one or more PSKs for use. </del> <ins> Key importers mirror the concept of key exporters in TLS in that they diversify a key based on some contextual information before use in a connection.  In contrast to key exporters, wherein differentiation is done via an explicit label and context string, the key importer defined herein uses an optional context string along with a target protocol and KDF identifier to differentiate an external PSK into one or more PSKs for use. </ins> Imported keys do not require negotiation for use, as a client and server will not agree upon identities if not imported correctly. Thus, importers induce no protocol changes with the exception of expanding the set of PSK identities sent on the wire.  Endpoints may incrementally deploy PSK importer support by offering non-imported <del> keys for TLS versions prior to TLS 1.3.  (Negotiation and use of imported PSKs requires both endpoints support the importer API described herein.) </del> <ins> keys for TLS versions prior to TLS 1.3.  Non-imported and imported PSKs are distinct since their identities are different on the wire. See rollout for more details. Clients which import external keys TLS MUST NOT use these keys for any other purpose.  Moreover, each external PSK MUST be associated with at most one hash function. </ins> 3.1. External PSK (EPSK): A PSK established or provisioned out-of-band, i.e., not from a TLS connection, which is a tuple of (Base Key, <del> External Identity, KDF).  The associated KDF (and hash function) may be undefined. </del> <ins> External Identity, Hash). </ins> Base Key: The secret value of an EPSK. External Identity: The identity of an EPSK. <del> Imported Identity: The identity of a PSK as sent on the wire. </del> <ins> Target protocol: The protocol for which a PSK is imported for use. Target KDF: The KDF for which a PSK is imported for use. Imported PSK (IPSK): A PSK derived from an EPSK, external identity, optional context string, and target protocol and KDF. Imported Identity: The identity of an Imported PSK as sent on the wire. </ins> 4. A key importer takes as input an EPSK with external identity <del> 'external_identity' and base key 'epsk', as defined in terminology, along with an optional label, and transforms it into a set of PSKs </del> <ins> "external_identity" and base key "epsk", as defined in terminology, along with an optional context, and transforms it into a set of PSKs </ins> and imported identities for use in a connection based on supported <del> HashAlgorithms.  In particular, for each supported HashAlgorithm 'hash', the importer constructs an ImportedIdentity structure as follows: [[TODO: An alternative design might combine label and hash into the same field so that future protocols which don't have a notion of HashAlgorithm don't need this field.]] ImportedIdentity.label MUST be bound to the protocol for which the key is imported.  Thus, TLS 1.3 and QUICv1 I-D.ietf-quic-transport MUST use "tls13" as the label.  Similarly, TLS 1.2 and all prior TLS versions should use "tls12" as ImportedIdentity.label, as well as SHA256 as ImportedIdentity.hash.  Note that this means future versions of TLS will increase the number of PSKs derived from an external PSK. </del> <ins> (target) protocols and KDFs.  In particular, for each supported target protocol "target_protocol" and KDF "target_kdf", the importer constructs an ImportedIdentity structure as follows: The list of "target_kdf" values is maintained by IANA as described in IANA.  External PSKs MUST NOT be imported for versions of (D)TLS 1.2 or prior versions.  See rollout for discussion on how imported PSKs for TLS 1.3 and non-imported PSKs for earlier versions co-exist for incremental deployment. </ins> ImportedIdentity.context MUST include the context used to derive the <del> EPSK, if any exists.  If the EPSK is a key derived from some other protocol or sequence of protocols, ImportedIdentity.context MUST include a channel binding for the deriving protocols RFC5056.  If any secrets are agreed in earlier protocols they SHOULD be included in ImportedIdentity.context CCB. </del> <ins> EPSK, if any exists.  For example, ImportedIdentity.context may include information about peer roles or identities to mitigate Selfie-style reflection attacks.  See mitigate-selfie for more details.  If the EPSK is a key derived from some other protocol or sequence of protocols, ImportedIdentity.context MUST include a channel binding for the deriving protocols RFC5056. </ins> <del> A unique and imported PSK (IPSK) with base key 'ipskx' bound to this identity is then computed as follows: </del> <ins> ImportedIdentity.target_protocol MUST be the (D)TLS protocol version for which the PSK is being imported.  For example, TLS 1.3 RFC8446 and QUICv1 QUIC use 0x0304.  Note that this means future versions of TLS will increase the number of PSKs derived from an external PSK. </ins> <del> [[TODO: The length of ipskx MUST match that of the corresponding and supported ciphersuites.]] </del> <ins> An Imported PSK derived from an EPSK with base key 'epsk' bound to this identity is then computed as follows: L is corresponds to the KDF output length of ImportedIdentity.target_kdf as defined in IANA.  For hash-based KDFs, such as HKDF_SHA256(0x0001), this is the length of the hash function output, i.e., 32 octets.  This is required for the IPSK to be of length suitable for supported ciphersuites. The identity of 'ipskx' as sent on the wire is ImportedIdentity. </ins> The hash function used for HKDF RFC5869 is that which is associated <del> with the external PSK.  It is not bound to ImportedIdentity.hash.  If no hash function is specified, SHA-256 MUST be used.  Differentiating epsk by ImportedIdentity.hash ensures that each imported PSK is only used with at most one hash function, thus satisfying the requirements in RFC8446.  Endpoints MUST import and derive an ipsk for each hash function used by each ciphersuite they support.  For example, importing a key for TLS_AES_128_GCM_SHA256 and TLS_AES_256_GCM_SHA384 would yield two PSKs, one for SHA256 and another for SHA384.  In contrast, if TLS_AES_128_GCM_SHA256 and TLS_CHACHA20_POLY1305_SHA256 are supported, only one derived key is necessary. </del> <ins> with the EPSK.  It is not the hash function associated with ImportedIdentity.target_kdf.  If no hash function is specified, SHA-256 MUST be used.  Diversifying EPSK by ImportedIdentity.target_kdf ensures that an IPSK is only used as input keying material to at most one KDF, thus satisfying the requirements in RFC8446. Endpoints generate a compatible ipskx for each target ciphersuite they offer.  For example, importing a key for TLS_AES_128_GCM_SHA256 and TLS_AES_256_GCM_SHA384 would yield two PSKs, one for HKDF-SHA256 and another for HKDF-SHA384.  In contrast, if TLS_AES_128_GCM_SHA256 and TLS_CHACHA20_POLY1305_SHA256 are supported, only one derived key is necessary. </ins> The resulting IPSK base key 'ipskx' is then used as the binder key in TLS 1.3 with identity ImportedIdentity.  With knowledge of the <del> supported hash functions, one may import PSKs before the start of a connection. </del> <ins> supported KDFs, one may import PSKs before the start of a connection. </ins> EPSKs may be imported for early data use if they are bound to protocol settings and configurations that would otherwise be required

Discussion:
Also, remove PSK identity encryption sketch. This was underspecified and can always be done in a separate draft if desired. cc NAME
Note, we might want to simply consider merging and into the same field, since we have the registry. That'd save us two bytes. It seems to boil down to whether keeping protocol and KDF separate is better (long term) than merging them. I'm not sure what's the best answer there.
In reviewing the draft, it was not clear that the input PSK was also bound to a hash function, but that the process could produce a derived PSK bound to any hash function. As this is the primary function of the document, that seems problematic. This seems to be the answer to this: I assume that this means KDF rather than hash function throughout (the KDF-bound thing seems right). But it never really comes out and says that:
It's optionally bound to a hash function. In defining external PSKs, the document states that the "associated KDF (and hash function) may be undefined." Do you think it would be more clear to put this in the introduction? (Minimally, as you point out, the introduction should not assume that the KDF-bound PSK as input is not quite right, so that should probably change.) As for the second comment (KDF output), that's not quite the goal. The importer binds input PSKs to a hash via HKDF, not via the TLS KDF. (The TLS 1.2 PRF is not used.) To summarize, my view is that the contract is more or less: optional hash-bound PSK in, hash-bound PSK out. Does that seem incorrect to you?
Hmm, my understanding is that we don't have any support for optional bindings on input. I'm inclined to say that we need stricter constraints on input. Namely that they are bound to a specific KDF and that KDF needs to take a context struct in the form this document describes. Yes, limiting the concrete instantiation of this to HKDF is fine (and the only sensible option). However, if you don't bind the input key more tightly, you will end up shifting the problem you set out to solve one step further out.
I'm not sure I follow anymore!

In my view, the problem we aim to solve is one where the external PSK was not provisioned "correctly," i.e., with the right hash (KDF) function in mind. It's just a plain old n-bit key. The importer is designed to bind this key to a specific hash (KDF). If the machinery that produced external PSKs was forced to change to accommodate a KDF, then it seems that machinery could also vend PSKs that do precisely what the importer does. Namely, differentiate by (context, protocol, hash/KDF). Perhaps I'm misunderstanding your point. Can you please clarify? If you're looking for a specific change, could you please make a suggestion?
In TLS 1.2 and earlier, PSKs were not bound to a KDF. Now in TLS 1.3, a PSK is required to be bound to a KDF. I thought the goal was to start with a PSK that is not bound to a KDF and produce a PSK that can be used in the desired context. So, the input is always a "plain old n-bit key" (Chris' words). The output, when the desired context is TLS 1.3, must include the KDF.
This is exactly what the importer does. Do you disagree?
It is not clear to me what the importer does if I want the context to be TLS 1.2.
It changes the info (or proposed field) value and runs the key through a KDF.
Right, I get that, which I think will bind the resulting key to TLS 1.2 but allow any KDF. I think the document should be explicit if that is the case.
Looking at , I think that you want to bind this to the TLS 1.2 KDF differently by using a different KDF identifier. The identifiers you have are only good for TLS 1.3. You probably also want security considerations addressing the fact that the TLS 1.2 has this odd characteristic.
Can you please elaborate on why you think this is necessary? As an example, let's assume I have an external PSK and I want to import it for TLS 1.2 and 1.3, and I only want to support one hash function (or KDF).

Let's say that KDF is HKDFSHA256. I would then import with the tuples (0x0303, HKDFSHA256) and (0x0304, HKDF_SHA256), and it seems I'd have the desired separation. In other words, I don't see a technical reason why importing PSKs for TLS 1.2 requires use of the TLS 1.2 KDF (PRF).
I think we are saying the same thing, except that when the protocol version is TLS 1.2 that it will be used with the PRF that is negotiated in the TLS 1.2 handshake. I just think the document should say that explicitly.
This is not what I'm saying! I'm saying do not use the TLS 1.2 PRF. Rather, use HKDF. Can you clarify why you think the TLS 1.2 PRF should be used instead?
There are two thongs that are being confused here. First, the one used to create the protocol-version-specific key. This is the one that you want to always use HKDF. That is fine with me. Second, with TLS 1.3, it is bound to a particular hash function. I am saying that when a key is derived for TLS 1.2 the document should say that it will be used with whatever PRF is negotiated in the TLS handshake. Right now, I do d not see anything about that in the document at all.
Thanks for clarifying! If I use an external PSK for TLS 1.2 today, without any import step, it's already the case that it's used with whatever PRF is negotiated in the handshake. The importer does not change that, and therefore I think doesn't need to say anything about that.
I just think it would be better say it since TLS 1.3 had a binding but TLS 1.2 does not. You now understand my point, so I will leave it to you as author.
Why is this a string and not the protocol version number? It sure would be easier that way.
In writing up , I realize now that the combination of label and hash is what is being used to identify a KDF.

I think that you need to allow for the HashAlgorithm to be replaced by something else in future, or for different applications other than TLS. It might pay to instead reduce the tuple of (tls version, hash) to a single identifier that says tls12sha256, tls13hkdf_sha256, which might broaden applicability. That could be crazy-talk though. Either way, it would be good to understand this better in the abstract so as to make a less ad hoc decision about what is being identified and then how.
I agree that the info string should probably just be a version. I imagine there will be a use case where someone wants to, say, import with the label "tls1.3 special sauce". In this case, they could KDF their PSK with the the "special sauce" string and then import with the "tls13" label. And at this point, it might as well be the version number. Let's assume this will be done, and the new field will be called "protocol". So that leaves the question about what to do for the hash algorithm. As you point out, removing the HashAlgorithm dependency would be good for the future. (We have a TODO in the draft to that effect.) So the question then becomes, do we merge whatever this new field is with "protocol," or do we keeep them separate. I don't think we need to merge these for "level matching" or other purposes, so I'm inclined to keep them separate. Thus, how about thee following structure: Where is a uint16 codepoint, and is an HPKE-like identifier [1] for a KDF, such as HKDF-SHA256 or HKDF-SHA512. We could then define the importer with respect to this KDF, rather than baking in a hard dependency on HKDF. NAME NAME what do you think? [1] URL
That seems reasonable - I think that there is some reason to separate target protocol (and version, as a tuple) from the KDF that is used. There's a trick in terms of finding the right level of identification to use for the protocol. I suspect that seeding a registry with 0x0304 and 0x0303 is probably the right way to do this.

That has the effect of constraining TLS a little in how it chooses version numbers, but I think that's workable. (As a reminder, in case you thought this was too easy, QUIC uses a 32-bit identifier for versions. We'd need a mapping QUIC. NAME just makes this look simple.)
Currently the context binds the info string (or proposed field) to the version of TLS, not to the calling application or protocol (QUIC).
Do we want to allow the output of the KDF to be anything other that Nh? Right now, L is always URL, which I think i the Nh value associated with the HPKE-like identifier.
Correct! I can't see a reason why we'd want the KDF output to be something different. Do you have a use case in mind?
If we want this to apply to other contexts, like your special sauce above, it is unclear to me that URL is always the right answer.
The point of the importer is to produce a PSK for use in TLS. Why would the context affect the length?
The discussion of is opening this for other protocol contexts too.
NAME NAME I made the proposed change in . Please have a look!
Works for me