CARVIEW |
Navigation Menu
-
Notifications
You must be signed in to change notification settings - Fork 6.1k
8353578: Refactor existing usage of internal HKDF impl to use the KDF API #24393
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
👋 Welcome back valeriep! A progress list of the required criteria for merging this PR into |
@valeriepeng This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be:
You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been 739 new commits pushed to the
As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details. ➡️ To integrate this PR with the above commit message to the |
@valeriepeng The following label will be automatically applied to this pull request:
When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command. |
/contributor driverkt |
@valeriepeng Syntax:
User names can only be used for users in the census associated with this repository. For other contributors you need to supply the full name and email address. |
/contributor add driverkt |
@valeriepeng Syntax:
User names can only be used for users in the census associated with this repository. For other contributors you need to supply the full name and email address. |
Webrevs
|
/contributor add kdriver |
@valeriepeng |
= hkdf.extract(zeros, ikm, "TlsEarlySecret"); | ||
SecretKey earlySecret = hkdf.deriveKey("TlsEarlySecret", | ||
HKDFParameterSpec.ofExtract().addSalt(zeros) | ||
.addIKM(ikm).extractOnly()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe no need for addSalt(zeros)
. I remember salt is by default zeros for HKDF.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I am on the fence about this. Given the specified value is the same as the default, it can be removed. I kept it there so the new code matches the original code completely. Not much difference either way I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like having it there to communicate that is really the intent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The rest looks good.
Nice to get this done finally!
static String digest2HKDF(String digestAlg) throws SSLHandshakeException { | ||
String sanitizedAlg = digestAlg.replace("-", ""); | ||
return switch (sanitizedAlg) { | ||
case "SHA256", "SHA384", "SHA512" -> "HKDF-" + sanitizedAlg; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a nit, but currently we don't have SHA512 in CipherSuite.HashAlg
. You can leave it for any future enhancements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could also consider storing the HKDF algorithm names in the HashAlg
enum. Not sure if it would make much difference, performance wise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this sounds better for overall consistency. Will adopt the HashAlg
enum suggestion.
= hkdf.extract(zeros, ikm, "TlsEarlySecret"); | ||
SecretKey earlySecret = hkdf.deriveKey("TlsEarlySecret", | ||
HKDFParameterSpec.ofExtract().addSalt(zeros) | ||
.addIKM(ikm).extractOnly()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like having it there to communicate that is really the intent.
refactored code to remove unused AlgorithmParameterSpec argument.
@@ -292,8 +292,7 @@ class RSAKAKeyDerivation implements SSLKeyDerivation { | |||
} | |||
|
|||
@Override | |||
public SecretKey deriveKey(String algorithm, | |||
AlgorithmParameterSpec params) throws IOException { | |||
public SecretKey deriveKey(String type) throws IOException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about changing the variable name to typeNotUsed
like SSLKeyDerivation.LegacyMasterKeyDerivation
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, missed this one.
AlgorithmParameterSpec params) throws IOException; | ||
SecretKey deriveKey(String purpose) throws IOException; | ||
|
||
default byte[] deriveData(String purpose) throws IOException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an internal interface, so I don't think you need to make this a default
method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't add deriveData(String)
impl to all the existing impls of SSLKeyDerivation
. Only impls used for deriving IVs are updated to add impl for deriveData(String)
, so the default
method is necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still looks good.
The changes here are not enough to get the NSS-FIPS library to complete TLS1.3 handshake, but it's a big step in the right direction. I think we can fix the remaining issues separately.
cleanup interim security values changed PKCS11 HKDF impl to allow key algorithms starting with "Tls" as generic keys
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still LGTM.
} finally { | ||
if (eae_prk instanceof SecretKeySpec s) { | ||
SharedSecrets.getJavaxCryptoSpecAccess() | ||
.clearSecretKeySpec(s); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wish we could use s.destroy()
here instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it'd be nice. I reopened https://bugs.openjdk.org/browse/JDK-8160206 and we can address this separately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or in the meantime:
} finally {
// Best effort
if (eae_prk instanceof SecretKeySpec s) {
SharedSecrets.getJavaxCryptoSpecAccess()
.clearSecretKeySpec(s);
} else {
try {
eae_prk.destroy();
} catch (DestroyFailedException e) {
// swallow
}
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, thanks for the suggestion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is what I did in my Exporter code. Assuming you go in first, I'll update mine to use your Util method.
|
||
// derive handshake secret | ||
return hkdf.extract(saltSecret, sharedSecret, algorithm); | ||
return hkdf.deriveKey(type, HKDFParameterSpec.ofExtract() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The line above may fail because the hkdf
object has been used once on line 121 with zero-valued salt and IKM, which selected the software-based HKDF from SunJCE. At this point, however, sharedSecret
is the result of ECDH and may be non-extractable if produced by an HSM, making it incompatible with the SunJCE implementation. To avoid this issue, get a new hkdf
by calling hkdf = KDF.getInstance(hashAlg.hkdfAlgorithm)
before this line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, ok, interesting scenario. I add another KDF.getInstance()
call as you suggested just to be safe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that deserves a code comment; it's far from obvious why we do that. Also, we will make P11 work with zero-valued salt soon.
hkdfInfo, hashAlg.hashLength, "TlsBinderKey"); | ||
return hkdf.deriveKey("TlsBinderKey", | ||
HKDFParameterSpec.expandOnly(earlySecret, hkdfInfo, | ||
hashAlg.hashLength)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to combine the 2 deriveKey
calls above into a single Extract-Then-Expand call? Then you don't need to clean up earlySecret
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be possible, let me give it a try. Thanks~
@@ -810,18 +834,19 @@ private static byte[] computeBinder(HandshakeContext context, | |||
|
|||
private static SecretKey deriveBinderKey(HandshakeContext context, | |||
SecretKey psk, SSLSessionImpl session) throws IOException { | |||
SecretKey earlySecret = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unused variable, can delete.
@@ -35,9 +35,11 @@ | |||
import java.security.spec.AlgorithmParameterSpec; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can remove import now.
@@ -620,7 +622,7 @@ public byte[] produce(ConnectionContext context, | |||
|
|||
SSLKeyDerivation handshakeKD = ke.createKeyDerivation(shc); | |||
SecretKey handshakeSecret = handshakeKD.deriveKey( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like this can be cleared after it is used to derive the key. Similar comment on line 1310.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I am not sure if clearing handshakeSecret
is ok - this handshakeSecret
is passed to kd
on line 636 and stored internally without cloning. Then kd
is stored into shc
which suggests that it may be used later. Clearing it will likely cause problems for subsequent key derivations? Same goes for line 1310. Is there something that I missed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yes you are right.
Missing test plan in the PR Description. (i.e. tier1/tier2/JCK?) |
I always run tier 1-3 tests for all of my PRs. Don't anticipate that this would affect JCK, but will give it a try just in case. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a few comments to think about, and one copyright nit.
try { | ||
HKDFParameterSpec spec = | ||
HKDFParameterSpec.ofExtract().addIKM(s).extractOnly(); | ||
return hkdf.deriveKey("Generic", spec); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't done much with DHKEM yet, but should the returned key have algorithm name of "Generic," or something more descriptive like the previous "HKDF-PRK"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Me neither. However, given HKDF-PRK
is not a standard algorithm and also not recognized by the SunPKCS11
provider, I changed it to Generic
. Existing HKDF
impl in the SunPKCS11
provider is quite strict about the derived key algorithms and it will error out unless we add HKDF-PRK
to be a recognized key algorithm for key derivation. Given these reasons, it seems Generic
is the better choice here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is any specific salt needed here like in TLS?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should chat next week about an issue Weijun raised and the algorithm names in the Exporters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copyright update.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will fix.
byte[] zeros = new byte[hashAlg.hashLength]; | ||
SecretKey earlySecret = hkdf.extract(zeros, psk, "TlsEarlySecret"); | ||
KDF hkdf = KDF.getInstance(hashAlg.hkdfAlgorithm); | ||
SecretKey earlySecret = hkdf.deriveKey("TlsEarlySecret", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a little worried that the proper number of salt zeros are now expected to be known in the KDF deriveKey code instead of specified specifically here (and in other similar places). Should we consider specifying them here and the other places instead to play it safe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just found that we had talked about this previously. What was your reasoning for pulling it?
Call me paranoid, but I'm not seeing where the JDK 24 javadocs discuss what happens if salt is not supplied. RFC 8446/Section 7.1 states:
- "0" indicates a string of Hash.length bytes set to zero.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I will add it back just to be safe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought there were other locations, but maybe I was just imagining it! ;)
try { | ||
HKDFParameterSpec spec = | ||
HKDFParameterSpec.ofExtract().addIKM(s).extractOnly(); | ||
return hkdf.deriveKey("Generic", spec); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is any specific salt needed here like in TLS?
@@ -445,5 +448,23 @@ public static boolean isSupportedKeyAgreementOutputAlgorithm(String alg) { | |||
return alg.equalsIgnoreCase("TlsPremasterSecret") | |||
|| alg.equalsIgnoreCase("Generic"); | |||
} | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As you know, I've been working on the TLS Exporters change which will use the same KDF APIs. I've already updated that to use your style.
Looks like I've now got one more thing to change! ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a reply to the comment above. I don't know why GitHub does not show a reply box there.
Is any specific salt needed here like in TLS?
In DHKEM, the salt used is always empty.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, no need to explicitly set it I assume? As this is a refactoring, I prefer to minimize changes unless consensus is different.
byte[] zeros = new byte[hashAlg.hashLength]; | ||
SecretKey earlySecret = hkdf.extract(zeros, psk, "TlsEarlySecret"); | ||
KDF hkdf = KDF.getInstance(hashAlg.hkdfAlgorithm); | ||
SecretKey earlySecret = hkdf.deriveKey("TlsEarlySecret", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought there were other locations, but maybe I was just imagining it! ;)
} finally { | ||
if (eae_prk instanceof SecretKeySpec s) { | ||
SharedSecrets.getJavaxCryptoSpecAccess() | ||
.clearSecretKeySpec(s); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is what I did in my Exporter code. Assuming you go in first, I'll update mine to use your Util method.
try { | ||
HKDFParameterSpec spec = | ||
HKDFParameterSpec.ofExtract().addIKM(s).extractOnly(); | ||
return hkdf.deriveKey("Generic", spec); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should chat next week about an issue Weijun raised and the algorithm names in the Exporters.
You're probably good to go, but might check with Weijun/Sean/DJ in case there's anything last minute. |
I'll be looking too, but don't hold up integration to wait for my review. |
|
||
// derive handshake secret | ||
return hkdf.extract(saltSecret, sharedSecret, algorithm); | ||
// NOTE: do not reuse the HKDF object for "TlsEarlySecret" for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: There is no longer an "HKDF object". Might be worth updating this comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It actually means the "KDF object w/ HKDF algorithm", I can see how that may look confusing. I can change it to "KDF object", but then it'd require everyone to re-review again. Given this is just a nit, can we leave it for later?
this.hkdfInfo = createHkdfInfo(label, context, hashAlg.hashLength); | ||
this.keyLen = hashAlg.hashLength; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very minor nit: might be worth accessing this field once and passing this.keyLen
to createHkdfInfo
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I like to match the ordering in the constructor with the ordering of fields. Since this is relatively minor, I am inclined to leave it as is...
((SecretSizeSpec)keySpec).length, algorithm); | ||
KDF hkdf = KDF.getInstance(hkdfAlg); | ||
return hkdf.deriveKey(type, | ||
HKDFParameterSpec.expandOnly(secret, hkdfInfo, keyLen)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed we have done away with the AlgorithmParameterSpec
parameter to this method. While the new implementation makes sense in light of the new parameter set, I wonder whether there was a deliberate use-case lost here...
In the previous implementation, presumably, if the keySpec
's length was shorter than the expanded value, only a portion of the value would be used. With the new implementation, the hash algorithm's length of bytes is always used.
I'm not sure how relevant this is, but it could be worth noting. If we had kept the old parameter, I suppose we could have used deriveData
and truncated the result to ((SecretSizeSpec)keySpec).length
before returning a SecretKey
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Searching among the JSSE code base, it looks that SSLBasicKeyDerivation
class is only used in Finished
class and the particular SecretSizeSpec
value is always same as the current keyLen
field. There is no other usage as far as I can tell. If we ended up using SSLBasicKeyDerivation
class with a shorter key length, we can add an explicit keyLen
argument to the SSLBasicKeyDerivation
constructor. I see no need for the SecretSizeSpec
class given the code flow in Finished
class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See inline.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approving based on above discussion.
/integrate |
Going to push as commit 4c0a0ab.
Your commit was automatically rebased without conflicts. |
@valeriepeng Pushed as commit 4c0a0ab. 💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored. |
This PR removes the internal JSSE HKDF impl and changes to use the KDF API for the HKDF support from JCA/JCE providers.
This is just code refactoring. Known-answer regression test for the internal JSSE HKDF impl is removed as the test vectors are already covered by the HKDF impl in SunJCE provider.
JPRT Tier1-3 result: https://mach5.us.oracle.com/mdash/jobs/vpeng-jdkOh3-20250512-2316-27739411
Thanks in advance for the review~
Progress
Issue
Reviewers
Contributors
<kdriver@openjdk.org>
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/24393/head:pull/24393
$ git checkout pull/24393
Update a local copy of the PR:
$ git checkout pull/24393
$ git pull https://git.openjdk.org/jdk.git pull/24393/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 24393
View PR using the GUI difftool:
$ git pr show -t 24393
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/24393.diff
Using Webrev
Link to Webrev Comment