Skip to content

Commit 1080579

Browse files
committed
KeyAgreement now reports the IllegalStateException javax.crypto.KeyAgreement specifies when a shared secret is asked for before doPhase has completed the agreement, rather than returning whatever each SPI's result field held, and the DH SPI no longer holds the private value in that field.
1 parent 0a579db commit 1080579

10 files changed

Lines changed: 72 additions & 2 deletions

File tree

‎docs/releasenotes.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ Date: 2026, TBD
1515

1616
### 2.1.2 Defects Fixed
1717

18+
- A KeyAgreement asked for its shared secret before doPhase returned data rather than refusing. javax.crypto.KeyAgreement specifies IllegalStateException for that state, but nothing in the provider tracked it, so each SPI handed back whatever its result field held: for Diffie-Hellman that was the private value itself - engineInit seeded result with x, so generateSecret() returned the private exponent padded to the prime's length and generateSecret("AES") an all-zero key taken from that padding - while ECDH returned null and its named-algorithm overload raised NullPointerException. BaseAgreementSpi now records whether a doPhase has completed the agreement since the last init and refuses the request with an IllegalStateException naming the algorithm, so every family in the provider - DH, ECDH and ECMQV, the SM2 exchange, both ECGOST families, XDH, SM9 and NewHope - answers the same way, and the DH SPI no longer holds the private value in that field at all.
19+
1820
### 2.1.3 Additional Features and Functionality
1921

2022
### 2.1.4 Additional Notes

‎prov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/dh/KeyAgreementSpi.java‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ protected Key engineDoPhase(
153153

154154
result = unifiedAgreement.calculateAgreement(pKey);
155155

156+
agreementCompleted();
157+
156158
return null;
157159
}
158160
else if (mqvAgreement != null)
@@ -169,6 +171,8 @@ else if (mqvAgreement != null)
169171

170172
result = bigIntToBytes(mqvAgreement.calculateAgreement(pKey));
171173

174+
agreementCompleted();
175+
172176
return null;
173177
}
174178
else
@@ -191,6 +195,8 @@ else if (mqvAgreement != null)
191195

192196
if (lastPhase)
193197
{
198+
agreementCompleted();
199+
194200
return null;
195201
}
196202

@@ -337,14 +343,15 @@ else if (params instanceof UserKeyingMaterialSpec)
337343
}
338344

339345
this.x = privKey.getX();
340-
this.result = bigIntToBytes(x);
341346
}
342347

343348
protected void engineInit(
344349
Key key,
345350
SecureRandom random)
346351
throws InvalidKeyException
347352
{
353+
resetAgreement();
354+
348355
if (!(key instanceof DHPrivateKey))
349356
{
350357
throw new InvalidKeyException("DHKeyAgreement requires DHPrivateKey");
@@ -356,7 +363,6 @@ protected void engineInit(
356363
this.g = privKey.getParams().getG();
357364
this.x = privKey.getX();
358365
this.random = CryptoServicesRegistrar.getSecureRandom(random);
359-
this.result = bigIntToBytes(x);
360366
}
361367

362368
protected byte[] doCalcSecret()

‎prov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ec/GMKeyExchangeSpi.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ protected Key engineDoPhase(
6363

6464
result = engine.calculateKey(128, parameters);
6565

66+
agreementCompleted();
67+
6668
return null;
6769
}
6870

‎prov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ec/KeyAgreementSpi.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ public Throwable getCause()
158158
};
159159
}
160160

161+
agreementCompleted();
162+
161163
return null;
162164
}
163165

‎prov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost/KeyAgreementSpi.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ public Throwable getCause()
8888
};
8989
}
9090

91+
agreementCompleted();
92+
9193
return null;
9294
}
9395

‎prov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost12/KeyAgreementSpi.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ public Throwable getCause()
8484
};
8585
}
8686

87+
agreementCompleted();
88+
8789
return null;
8890
}
8991

‎prov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/KeyAgreementSpi.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ protected Key engineDoPhase(Key key, boolean lastPhase)
151151
agreement.calculateAgreement(pub, result, 0);
152152
}
153153

154+
agreementCompleted();
155+
154156
return null;
155157
}
156158

‎prov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/sm9/KeyAgreementSpi.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ protected Key engineDoPhase(Key key, boolean lastPhase)
118118
throw SecurityExceptions.invalidKeyException(e.getMessage(), e);
119119
}
120120

121+
agreementCompleted();
122+
121123
return null;
122124
}
123125

‎prov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/util/BaseAgreementSpi.java‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ public abstract class BaseAgreementSpi
166166
protected byte[] ukmParameters;
167167
protected byte[] ukmParametersSalt;
168168
private HybridValueParameterSpec hybridSpec;
169+
private boolean completed;
169170

170171
public BaseAgreementSpi(String kaAlgorithm, DerivationFunction kdf)
171172
{
@@ -268,6 +269,8 @@ protected void engineInit(
268269
SecureRandom random)
269270
throws InvalidKeyException
270271
{
272+
resetAgreement();
273+
271274
try
272275
{
273276
doInitFromKey(key, null, random);
@@ -285,6 +288,8 @@ protected void engineInit(
285288
SecureRandom random)
286289
throws InvalidKeyException, InvalidAlgorithmParameterException
287290
{
291+
resetAgreement();
292+
288293
if (params instanceof HybridValueParameterSpec)
289294
{
290295
this.hybridSpec = (HybridValueParameterSpec)params;
@@ -298,6 +303,39 @@ protected void engineInit(
298303
}
299304
}
300305

306+
/**
307+
* Record that the agreement has run to completion and its shared secret may now be generated.
308+
* A subclass calls this on the successful return of the doPhase that completes the agreement.
309+
*/
310+
protected void agreementCompleted()
311+
{
312+
this.completed = true;
313+
}
314+
315+
/**
316+
* Require a fresh agreement before the next shared secret. Called by the engineInit methods
317+
* here; a subclass that overrides one of those rather than doInitFromKey must call this itself.
318+
*/
319+
protected void resetAgreement()
320+
{
321+
this.completed = false;
322+
}
323+
324+
/**
325+
* Refuse a shared secret asked for before the agreement has completed. javax.crypto.KeyAgreement
326+
* specifies IllegalStateException for that state; without this each SPI would hand back whatever
327+
* its result field happened to hold, which for DH was the private value itself.
328+
*
329+
* @throws IllegalStateException if no doPhase has completed the agreement since the last init.
330+
*/
331+
protected void checkAgreementCompleted()
332+
{
333+
if (!completed)
334+
{
335+
throw new IllegalStateException(kaAlgorithm + " key agreement: doPhase must be called before generateSecret");
336+
}
337+
}
338+
301339
protected byte[] engineGenerateSecret()
302340
throws IllegalStateException
303341
{
@@ -437,6 +475,8 @@ else if (kdf instanceof HKDFBytesGenerator)
437475

438476
private byte[] calcSecret()
439477
{
478+
checkAgreementCompleted();
479+
440480
if (hybridSpec != null)
441481
{
442482
// Set Z' to Z || T

‎prov/src/main/java/org/bouncycastle/pqc/jcajce/provider/newhope/KeyAgreementSpi.java‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ public KeyAgreementSpi()
3333
protected void engineInit(Key key, SecureRandom secureRandom)
3434
throws InvalidKeyException
3535
{
36+
resetAgreement();
37+
3638
if (key != null)
3739
{
3840
agreement = new NHAgreement();
@@ -67,19 +69,25 @@ protected Key engineDoPhase(Key key, boolean lastPhase)
6769

6870
shared = exchPair.getSharedValue();
6971

72+
agreementCompleted();
73+
7074
return new BCNHPublicKey((NHPublicKeyParameters)exchPair.getPublicKey());
7175
}
7276
else
7377
{
7478
shared = agreement.calculateAgreement(otherPartyKey.getKeyParams());
7579

80+
agreementCompleted();
81+
7682
return null;
7783
}
7884
}
7985

8086
protected byte[] engineGenerateSecret()
8187
throws IllegalStateException
8288
{
89+
checkAgreementCompleted();
90+
8391
byte[] rv = Arrays.clone(shared);
8492

8593
Arrays.fill(shared, (byte)0);
@@ -90,6 +98,8 @@ protected byte[] engineGenerateSecret()
9098
protected int engineGenerateSecret(byte[] bytes, int offset)
9199
throws IllegalStateException, ShortBufferException
92100
{
101+
checkAgreementCompleted();
102+
93103
System.arraycopy(shared, 0, bytes, offset, shared.length);
94104

95105
Arrays.fill(shared, (byte)0);

0 commit comments

Comments
 (0)