8353578: Refactor existing usage of internal HKDF impl to use the KDF API#24393
8353578: Refactor existing usage of internal HKDF impl to use the KDF API#24393valeriepeng wants to merge 8 commits intoopenjdk:masterfrom
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.
Maybe no need for addSalt(zeros). I remember salt is by default zeros for HKDF.
There was a problem hiding this comment.
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.
I like having it there to communicate that is really the intent.
bradfordwetmore
left a comment
There was a problem hiding this comment.
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.
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.
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.
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.
I like having it there to communicate that is really the intent.
refactored code to remove unused AlgorithmParameterSpec argument.
| @Override | ||
| public SecretKey deriveKey(String algorithm, | ||
| AlgorithmParameterSpec params) throws IOException { | ||
| public SecretKey deriveKey(String type) throws IOException { |
There was a problem hiding this comment.
How about changing the variable name to typeNotUsed like SSLKeyDerivation.LegacyMasterKeyDerivation?
There was a problem hiding this comment.
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.
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.
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.
djelinski
left a comment
There was a problem hiding this comment.
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
| } finally { | ||
| if (eae_prk instanceof SecretKeySpec s) { | ||
| SharedSecrets.getJavaxCryptoSpecAccess() | ||
| .clearSecretKeySpec(s); |
There was a problem hiding this comment.
I wish we could use s.destroy() here instead.
There was a problem hiding this comment.
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.
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.
Sounds good, thanks for the suggestion.
There was a problem hiding this comment.
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.
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.
Hmm, ok, interesting scenario. I add another KDF.getInstance() call as you suggested just to be safe.
There was a problem hiding this comment.
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.
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.
Should be possible, let me give it a try. Thanks~
|
|
||
| private static SecretKey deriveBinderKey(HandshakeContext context, | ||
| SecretKey psk, SSLSessionImpl session) throws IOException { | ||
| SecretKey earlySecret = null; |
There was a problem hiding this comment.
unused variable, can delete.
| @@ -35,9 +35,11 @@ | |||
| import java.security.spec.AlgorithmParameterSpec; | |||
| @@ -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.
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.
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?
|
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. |
bradfordwetmore
left a comment
There was a problem hiding this comment.
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.
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.
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.
Is any specific salt needed here like in TLS?
There was a problem hiding this comment.
We should chat next week about an issue Weijun raised and the algorithm names in the Exporters.
| 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.
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.
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.
Ok, I will add it back just to be safe.
There was a problem hiding this comment.
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.
Is any specific salt needed here like in TLS?
| return alg.equalsIgnoreCase("TlsPremasterSecret") | ||
| || alg.equalsIgnoreCase("Generic"); | ||
| } | ||
|
|
There was a problem hiding this comment.
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.
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.
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.
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.
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.
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.
Nit: There is no longer an "HKDF object". Might be worth updating this comment.
There was a problem hiding this comment.
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.
Very minor nit: might be worth accessing this field once and passing this.keyLen to createHkdfInfo instead.
There was a problem hiding this comment.
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.
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.
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.
driverkt
left a comment
There was a problem hiding this comment.
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
gitCheckout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/24393/head:pull/24393$ git checkout pull/24393Update a local copy of the PR:
$ git checkout pull/24393$ git pull https://git.openjdk.org/jdk.git pull/24393/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 24393View PR using the GUI difftool:
$ git pr show -t 24393Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/24393.diff
Using Webrev
Link to Webrev Comment