Skip to content

Commit eeef018

Browse files
committed
Merge branch 'main' of gitlab.cryptoworkshop.com:root/bc-java
2 parents 3968d4e + 6d7d611 commit eeef018

13 files changed

Lines changed: 294 additions & 27 deletions

File tree

‎core/src/main/java/org/bouncycastle/crypto/agreement/ECVKOAgreement.java‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.bouncycastle.crypto.params.ECDomainParameters;
99
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
1010
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
11+
import org.bouncycastle.crypto.params.ParametersWithRandom;
1112
import org.bouncycastle.crypto.params.ParametersWithUKM;
1213
import org.bouncycastle.math.ec.ECAlgorithms;
1314
import org.bouncycastle.math.ec.ECPoint;
@@ -32,7 +33,13 @@ public void init(CipherParameters key)
3233
{
3334
ParametersWithUKM p = (ParametersWithUKM)key;
3435

35-
this.key = (ECPrivateKeyParameters)p.getParameters();
36+
CipherParameters kParam = p.getParameters();
37+
if (kParam instanceof ParametersWithRandom)
38+
{
39+
kParam = ((ParametersWithRandom)kParam).getParameters();
40+
}
41+
42+
this.key = (ECPrivateKeyParameters)kParam;
3643
this.ukm = new BigInteger(1, Arrays.reverse(p.getUKM()));
3744

3845
CryptoServicesRegistrar.checkConstraints(Utils.getDefaultProperties("ECVKO", this.key));

‎core/src/main/java/org/bouncycastle/crypto/engines/CryptoProWrapEngine.java‎

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,20 @@
77
import org.bouncycastle.crypto.params.ParametersWithRandom;
88
import org.bouncycastle.crypto.params.ParametersWithSBox;
99
import org.bouncycastle.crypto.params.ParametersWithUKM;
10+
import org.bouncycastle.util.Arrays;
1011
import org.bouncycastle.util.Pack;
1112

1213
public class CryptoProWrapEngine
1314
extends GOST28147WrapEngine
1415
{
1516
public void init(boolean forWrapping, CipherParameters param)
1617
{
18+
/*
19+
* The random is stripped on both sides of the UKM on purpose: UKM(Random(..)) is the documented
20+
* order, and Random(UKM(..)) is the earlier one, kept for existing callers. Not a pattern to copy -
21+
* see the org.bouncycastle.crypto.params package documentation. This repeats GOST28147WrapEngine,
22+
* whose init is handed a rebuilt UKM(..) with no random, so the unwrapping has to happen here too.
23+
*/
1724
if (param instanceof ParametersWithRandom)
1825
{
1926
ParametersWithRandom pr = (ParametersWithRandom)param;
@@ -23,20 +30,26 @@ public void init(boolean forWrapping, CipherParameters param)
2330
ParametersWithUKM pU = (ParametersWithUKM)param;
2431
byte[] sBox = null;
2532

33+
CipherParameters inner = pU.getParameters();
34+
if (inner instanceof ParametersWithRandom)
35+
{
36+
inner = ((ParametersWithRandom)inner).getParameters();
37+
}
2638

2739
KeyParameter kParam;
2840

29-
if (pU.getParameters() instanceof ParametersWithSBox)
41+
if (inner instanceof ParametersWithSBox)
3042
{
31-
kParam = (KeyParameter)((ParametersWithSBox)pU.getParameters()).getParameters();
32-
sBox = ((ParametersWithSBox)pU.getParameters()).getSBox();
43+
kParam = (KeyParameter)((ParametersWithSBox)inner).getParameters();
44+
sBox = ((ParametersWithSBox)inner).getSBox();
3345
}
3446
else
3547
{
36-
kParam = (KeyParameter)pU.getParameters();
48+
kParam = (KeyParameter)inner;
3749
}
3850

39-
kParam = new KeyParameter(cryptoProDiversify(kParam.getKey(), pU.getUKM(), sBox));
51+
// diversify a copy - getKey() is the caller's own array
52+
kParam = new KeyParameter(cryptoProDiversify(Arrays.clone(kParam.getKey()), pU.getUKM(), sBox));
4053

4154
if (sBox != null)
4255
{
@@ -94,7 +107,14 @@ private static byte[] cryptoProDiversify(byte[] K, byte[] ukm, byte[] sBox)
94107

95108
GCFBBlockCipher c = new GCFBBlockCipher(new GOST28147Engine());
96109

97-
c.init(true, new ParametersWithIV(new ParametersWithSBox(new KeyParameter(K), sBox), s));
110+
// with no S-box, the engine's default - the one the wrap itself then uses
111+
CipherParameters kParam = new KeyParameter(K);
112+
if (sBox != null)
113+
{
114+
kParam = new ParametersWithSBox(kParam, sBox);
115+
}
116+
117+
c.init(true, new ParametersWithIV(kParam, s));
98118

99119
c.processBlock(K, 0, K, 0);
100120
c.processBlock(K, 8, K, 8);

‎core/src/main/java/org/bouncycastle/crypto/engines/GOST28147WrapEngine.java‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ public class GOST28147WrapEngine
1717

1818
public void init(boolean forWrapping, CipherParameters param)
1919
{
20+
/*
21+
* The random is stripped on both sides of the UKM on purpose: UKM(Random(..)) is the documented
22+
* order, and Random(UKM(..)) is the earlier one, kept for existing callers. Not a pattern to copy -
23+
* see the org.bouncycastle.crypto.params package documentation. CryptoProWrapEngine does the same.
24+
*/
2025
if (param instanceof ParametersWithRandom)
2126
{
2227
ParametersWithRandom pr = (ParametersWithRandom)param;
@@ -25,9 +30,15 @@ public void init(boolean forWrapping, CipherParameters param)
2530

2631
ParametersWithUKM pU = (ParametersWithUKM)param;
2732

28-
cipher.init(forWrapping, pU.getParameters());
33+
CipherParameters kParam = pU.getParameters();
34+
if (kParam instanceof ParametersWithRandom)
35+
{
36+
kParam = ((ParametersWithRandom)kParam).getParameters();
37+
}
38+
39+
cipher.init(forWrapping, kParam);
2940

30-
mac.init(new ParametersWithIV(pU.getParameters(), pU.getUKM()));
41+
mac.init(new ParametersWithIV(kParam, pU.getUKM()));
3142
}
3243

3344
public String getAlgorithmName()

‎core/src/main/java/org/bouncycastle/crypto/hpke/DHKEM.java‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator;
88
import org.bouncycastle.crypto.CryptoServicesRegistrar;
99
import org.bouncycastle.crypto.RawAgreement;
10-
import org.bouncycastle.crypto.agreement.BasicRawAgreement;
11-
import org.bouncycastle.crypto.agreement.ECDHCBasicAgreement;
1210
import org.bouncycastle.crypto.agreement.ECDHCRawAgreement;
1311
import org.bouncycastle.crypto.agreement.X25519Agreement;
1412
import org.bouncycastle.crypto.agreement.X448Agreement;

‎core/src/main/java/org/bouncycastle/crypto/params/ParametersWithUKM.java‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
import org.bouncycastle.crypto.CipherParameters;
44

5+
/**
6+
* Wrapper class for parameters which include User Keying Material (UKM).
7+
* <p>
8+
* Combined with a {@link ParametersWithRandom}, this goes on the outside:
9+
* {@code ParametersWithUKM(ParametersWithRandom(key))}. The UKM is consumed by the wrap engine or key
10+
* agreement, and anything inside it is passed on.
11+
*/
512
public class ParametersWithUKM
613
implements CipherParameters
714
{

‎core/src/main/java/org/bouncycastle/crypto/params/package-info.java‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@
1717
* <em>outside</em> everything consumed further down the stack. Outermost first, the order used
1818
* throughout the lightweight API and by the JCE provider when it builds these chains is:
1919
* <ul>
20-
* <li>{@code ParametersWithContext} or {@code ParametersWithID} - a signature context string
21-
* (ML-DSA, SLH-DSA and similar) or a signer identity (SM2, SM9), consumed by the signer.</li>
20+
* <li>{@code ParametersWithContext}, {@code ParametersWithID} or {@code ParametersWithUKM} - a
21+
* signature context string (ML-DSA, SLH-DSA and similar), a signer identity (SM2, SM9), or the
22+
* user keying material of the GOST 28147 wrap engines and the VKO key agreement, consumed by the
23+
* signer, wrap engine or agreement.</li>
2224
* <li>{@code ParametersWithRandom} - consumed by the outermost component that needs randomness:
2325
* a signer, an asymmetric encoding, a wrap engine, or a padded buffered cipher. Block cipher
2426
* modes and stream ciphers do not unwrap it, so it must not be nested inside an IV.</li>
25-
* <li>{@code ParametersWithUKM} - the user keying material of the GOST 28147 wrap engines.</li>
2627
* <li>{@code ParametersWithIV} - the IV or nonce, consumed by the cipher mode, stream cipher or
2728
* MAC.</li>
2829
* <li>{@code ParametersWithSBox} - the GOST 28147 S-box, consumed by the engine and so placed
@@ -31,7 +32,9 @@
3132
* {@link org.bouncycastle.crypto.params.AsymmetricKeyParameter}.</li>
3233
* </ul>
3334
* So, for example, a GOST 28147 CBC cipher takes {@code IV(SBox(key))}, a padded CBC cipher takes
34-
* {@code Random(IV(key))}, and an ML-DSA signer takes {@code Context(Random(privateKey))}.
35+
* {@code Random(IV(key))}, an ML-DSA signer takes {@code Context(Random(privateKey))}, and a
36+
* GOST 28147 wrap engine takes {@code UKM(Random(SBox(key)))}. The GOST 28147 wrap engines also
37+
* still accept the earlier {@code Random(UKM(SBox(key)))}.
3538
* <p>
3639
* {@code ParametersWithSalt} is used only by
3740
* {@link org.bouncycastle.crypto.signers.ISO9796d2PSSSigner}, which accepts it as an alternative
@@ -42,5 +45,11 @@
4245
* {@link org.bouncycastle.crypto.params.AEADParameters} is a self-contained alternative to
4346
* {@code ParametersWithIV} for AEAD modes and combines key, nonce, tag length and associated data
4447
* without nesting.
48+
* <p>
49+
* Rejecting other nestings is intended. New code should accept the order above and nothing else.
50+
* Accepting an alternative order is a compatibility measure, and is justified only where existing
51+
* callers are known to build it, as with the earlier {@code Random(UKM(..))} of the GOST 28147 wrap
52+
* engines. An alternative added speculatively becomes a second form that every caller, wrapper and
53+
* port then has to keep supporting.
4554
*/
4655
package org.bouncycastle.crypto.params;
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package org.bouncycastle.crypto.test;
2+
3+
import org.bouncycastle.crypto.CipherParameters;
4+
import org.bouncycastle.crypto.engines.CryptoProWrapEngine;
5+
import org.bouncycastle.crypto.engines.GOST28147Engine;
6+
import org.bouncycastle.crypto.params.KeyParameter;
7+
import org.bouncycastle.crypto.params.ParametersWithSBox;
8+
import org.bouncycastle.crypto.params.ParametersWithUKM;
9+
import org.bouncycastle.util.encoders.Hex;
10+
import org.bouncycastle.util.test.SimpleTest;
11+
12+
/**
13+
* {@link CryptoProWrapEngine} (RFC 4357 sec. 6.3, with the sec. 6.5 KEK diversification) given no
14+
* S-box uses the GOST 28147 engine's default one throughout, and never modifies the caller's key.
15+
*/
16+
public class CryptoProWrapEngineTest
17+
extends SimpleTest
18+
{
19+
private static final byte[] KEK = Hex.decode("8b2f3a41c6d5e7091a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f7081");
20+
private static final byte[] CEK = Hex.decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20");
21+
private static final byte[] UKM = Hex.decode("1d80603c8544c727");
22+
23+
public String getName()
24+
{
25+
return "CryptoProWrapEngine";
26+
}
27+
28+
public void performTest()
29+
throws Exception
30+
{
31+
defaultSBoxTest();
32+
33+
keyUnmodifiedTest(null);
34+
keyUnmodifiedTest(GOST28147Engine.getSBox("E-A"));
35+
}
36+
37+
private void defaultSBoxTest()
38+
throws Exception
39+
{
40+
CryptoProWrapEngine engine = new CryptoProWrapEngine();
41+
42+
engine.init(true, new ParametersWithUKM(
43+
new ParametersWithSBox(new KeyParameter(KEK), GOST28147Engine.getSBox("Default")), UKM));
44+
byte[] expected = engine.wrap(CEK, 0, CEK.length);
45+
46+
engine.init(true, new ParametersWithUKM(new KeyParameter(KEK), UKM));
47+
isTrue("no S-box wrap differs from the default S-box", areEqual(expected, engine.wrap(CEK, 0, CEK.length)));
48+
49+
engine.init(false, new ParametersWithUKM(new KeyParameter(KEK), UKM));
50+
isTrue("no S-box unwrap failed", areEqual(CEK, engine.unwrap(expected, 0, expected.length)));
51+
}
52+
53+
private void keyUnmodifiedTest(byte[] sBox)
54+
throws Exception
55+
{
56+
String label = sBox == null ? "no S-box" : "S-box";
57+
58+
KeyParameter key = new KeyParameter(KEK);
59+
CipherParameters kParam = key;
60+
if (sBox != null)
61+
{
62+
kParam = new ParametersWithSBox(kParam, sBox);
63+
}
64+
ParametersWithUKM params = new ParametersWithUKM(kParam, UKM);
65+
66+
CryptoProWrapEngine engine = new CryptoProWrapEngine();
67+
68+
engine.init(true, params);
69+
byte[] wrapped = engine.wrap(CEK, 0, CEK.length);
70+
isTrue(label + ": caller's key modified by init", areEqual(KEK, key.getKey()));
71+
72+
engine.init(true, params);
73+
isTrue(label + ": wrap differs on re-init with the same parameters",
74+
areEqual(wrapped, engine.wrap(CEK, 0, CEK.length)));
75+
76+
engine.init(false, params);
77+
isTrue(label + ": unwrap with the same parameters failed", areEqual(CEK, engine.unwrap(wrapped, 0, wrapped.length)));
78+
isTrue(label + ": caller's key modified by re-init", areEqual(KEK, key.getKey()));
79+
}
80+
81+
public static void main(String[] args)
82+
{
83+
runTest(new CryptoProWrapEngineTest());
84+
}
85+
}

‎core/src/test/java/org/bouncycastle/crypto/test/RegressionTest.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ public class RegressionTest
158158
new SM2KeyExchangeTest(),
159159
new SM2SignerTest(),
160160
new ShortWrapCipherTextTest(),
161+
new UKMParametersNestingTest(),
162+
new CryptoProWrapEngineTest(),
161163
new SM4Test(),
162164
new SM9KEMTest(),
163165
new SM9EngineTest(),

0 commit comments

Comments
 (0)