Skip to content

Commit 7e65f22

Browse files
rootvector2gefeili
authored andcommitted
guard minimum length in sm2 decrypt and gost/dstu/desede/rc2 unwrap
1 parent cc63ade commit 7e65f22

8 files changed

Lines changed: 183 additions & 0 deletions

File tree

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,11 @@ public byte[] unwrap(byte[] in, int inOff, int inLen)
224224
throw new InvalidCipherTextException("Ciphertext not multiple of " + blockSize);
225225
}
226226

227+
if (inLen < 2 * blockSize)
228+
{
229+
throw new InvalidCipherTextException("unwrap data too short");
230+
}
231+
227232
/*
228233
// Check if the length of the cipher text is reasonable given the key
229234
// type. It must be 40 bytes for a 168 bit key and either 32, 40, or

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,11 @@ public byte[] unwrap(byte[] in, int inOff, int inLen)
156156
throw new DataLengthException("unwrap data must be a multiple of " + engine.getBlockSize() + " bytes");
157157
}
158158

159+
if (inLen < engine.getBlockSize())
160+
{
161+
throw new InvalidCipherTextException("unwrap data too short");
162+
}
163+
159164
int n = 2 * inLen / engine.getBlockSize();
160165

161166
int V = (n - 1) * 6;

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ public byte[] wrap(byte[] input, int inOff, int inLen)
5454
public byte[] unwrap(byte[] input, int inOff, int inLen)
5555
throws InvalidCipherTextException
5656
{
57+
// wrapped key is 32 octets of encrypted key material followed by the MAC
58+
if (inLen < 32 + mac.getMacSize())
59+
{
60+
throw new InvalidCipherTextException("unwrap data too short");
61+
}
62+
5763
byte[] decKey = new byte[inLen - mac.getMacSize()];
5864

5965
cipher.processBlock(input, inOff, decKey, 0);

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,11 @@ public byte[] unwrap(byte[] in, int inOff, int inLen)
245245
+ engine.getBlockSize());
246246
}
247247

248+
if (inLen < 2 * engine.getBlockSize())
249+
{
250+
throw new InvalidCipherTextException("unwrap data too short");
251+
}
252+
248253
/*
249254
* // Check if the length of the cipher text is reasonable given the key //
250255
* type. It must be 40 bytes for a 168 bit key and either 32, 40, or //

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,11 @@ private byte[] encrypt(byte[] in, int inOff, int inLen)
176176
private byte[] decrypt(byte[] in, int inOff, int inLen)
177177
throws InvalidCipherTextException
178178
{
179+
if (inLen < (curveLength * 2 + 1) + digest.getDigestSize())
180+
{
181+
throw new InvalidCipherTextException("ciphertext too short");
182+
}
183+
179184
byte[] c1 = new byte[curveLength * 2 + 1];
180185

181186
System.arraycopy(in, inOff, c1, 0, c1.length);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ public class RegressionTest
154154
new SM2EngineTest(),
155155
new SM2KeyExchangeTest(),
156156
new SM2SignerTest(),
157+
new ShortWrapCipherTextTest(),
157158
new SM4Test(),
158159
new SM9KEMTest(),
159160
new SM9SignerTest(),
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package org.bouncycastle.crypto.test;
2+
3+
import java.math.BigInteger;
4+
5+
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
6+
import org.bouncycastle.crypto.InvalidCipherTextException;
7+
import org.bouncycastle.crypto.engines.DESedeWrapEngine;
8+
import org.bouncycastle.crypto.engines.DSTU7624WrapEngine;
9+
import org.bouncycastle.crypto.engines.GOST28147WrapEngine;
10+
import org.bouncycastle.crypto.engines.RC2WrapEngine;
11+
import org.bouncycastle.crypto.engines.SM2Engine;
12+
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
13+
import org.bouncycastle.crypto.params.ECDomainParameters;
14+
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
15+
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
16+
import org.bouncycastle.crypto.params.KeyParameter;
17+
import org.bouncycastle.crypto.params.ParametersWithUKM;
18+
import org.bouncycastle.crypto.params.RC2Parameters;
19+
import org.bouncycastle.math.ec.ECConstants;
20+
import org.bouncycastle.math.ec.ECCurve;
21+
import org.bouncycastle.math.ec.ECPoint;
22+
import org.bouncycastle.util.encoders.Hex;
23+
import org.bouncycastle.util.test.SimpleTest;
24+
import org.bouncycastle.util.test.TestRandomBigInteger;
25+
26+
/**
27+
* A ciphertext / wrapped-key shorter than the fixed overhead the engine strips
28+
* must be rejected with an {@link InvalidCipherTextException}, not an escaping
29+
* {@code NegativeArraySizeException} / {@code ArrayIndexOutOfBoundsException}
30+
* from the {@code new byte[inLen - overhead]} allocation.
31+
*/
32+
public class ShortWrapCipherTextTest
33+
extends SimpleTest
34+
{
35+
public String getName()
36+
{
37+
return "ShortWrapCipherText";
38+
}
39+
40+
private void sm2()
41+
throws Exception
42+
{
43+
BigInteger p = new BigInteger("8542D69E4C044F18E8B92435BF6FF7DE457283915C45517D722EDB8B08F1DFC3", 16);
44+
BigInteger a = new BigInteger("787968B4FA32C3FD2417842E73BBFEFF2F3C848B6831D7E0EC65228B3937E498", 16);
45+
BigInteger b = new BigInteger("63E4C6D3B23B0C849CF84241484BFE48F61D59A5B16BA06E6E12D1DA27C5249A", 16);
46+
BigInteger n = new BigInteger("8542D69E4C044F18E8B92435BF6FF7DD297720630485628D5AE74EE7C32E79B7", 16);
47+
BigInteger gx = new BigInteger("421DEBD61B62EAB6746434EBC3CC315E32220B3BADD50BDC4C4E6C147FEDD43D", 16);
48+
BigInteger gy = new BigInteger("0680512BCBB42C07D47349D2153B70C4E5D7FDFCBFA36EA1A85841B9E46E09A2", 16);
49+
50+
ECCurve curve = new ECCurve.Fp(p, a, b, n, ECConstants.ONE);
51+
ECPoint g = curve.createPoint(gx, gy);
52+
ECDomainParameters domainParams = new ECDomainParameters(curve, g, n);
53+
54+
ECKeyPairGenerator kpg = new ECKeyPairGenerator();
55+
kpg.init(new ECKeyGenerationParameters(domainParams, new TestRandomBigInteger("1649AB77A00637BD5E2EFE283FBF353534AA7F7CB89463F208DDBC2920BB0DA0", 16)));
56+
AsymmetricCipherKeyPair kp = kpg.generateKeyPair();
57+
58+
SM2Engine engine = new SM2Engine();
59+
engine.init(false, (ECPrivateKeyParameters)kp.getPrivate());
60+
61+
// C1 (65) + C3 (32) is 97 octets; anything shorter cannot hold C1 + digest.
62+
try
63+
{
64+
engine.processBlock(new byte[50], 0, 50);
65+
fail("SM2Engine: no exception on short ciphertext");
66+
}
67+
catch (InvalidCipherTextException e)
68+
{
69+
isEquals("ciphertext too short", e.getMessage());
70+
}
71+
}
72+
73+
private void gost28147Wrap()
74+
throws Exception
75+
{
76+
GOST28147WrapEngine engine = new GOST28147WrapEngine();
77+
engine.init(false, new ParametersWithUKM(new KeyParameter(new byte[32]), new byte[8]));
78+
79+
try
80+
{
81+
engine.unwrap(new byte[16], 0, 16);
82+
fail("GOST28147WrapEngine: no exception on short input");
83+
}
84+
catch (InvalidCipherTextException e)
85+
{
86+
isEquals("unwrap data too short", e.getMessage());
87+
}
88+
}
89+
90+
private void dstu7624Wrap()
91+
throws Exception
92+
{
93+
DSTU7624WrapEngine engine = new DSTU7624WrapEngine(128);
94+
engine.init(false, new KeyParameter(new byte[16]));
95+
96+
try
97+
{
98+
engine.unwrap(new byte[0], 0, 0);
99+
fail("DSTU7624WrapEngine: no exception on empty input");
100+
}
101+
catch (InvalidCipherTextException e)
102+
{
103+
isEquals("unwrap data too short", e.getMessage());
104+
}
105+
}
106+
107+
private void desedeWrap()
108+
throws Exception
109+
{
110+
DESedeWrapEngine engine = new DESedeWrapEngine();
111+
engine.init(false, new KeyParameter(Hex.decode("0123456789abcdeffedcba987654321089abcdef01234567")));
112+
113+
try
114+
{
115+
engine.unwrap(new byte[8], 0, 8);
116+
fail("DESedeWrapEngine: no exception on short input");
117+
}
118+
catch (InvalidCipherTextException e)
119+
{
120+
isEquals("unwrap data too short", e.getMessage());
121+
}
122+
}
123+
124+
private void rc2Wrap()
125+
throws Exception
126+
{
127+
RC2WrapEngine engine = new RC2WrapEngine();
128+
engine.init(false, new RC2Parameters(new byte[16]));
129+
130+
try
131+
{
132+
engine.unwrap(new byte[8], 0, 8);
133+
fail("RC2WrapEngine: no exception on short input");
134+
}
135+
catch (InvalidCipherTextException e)
136+
{
137+
isEquals("unwrap data too short", e.getMessage());
138+
}
139+
}
140+
141+
public void performTest()
142+
throws Exception
143+
{
144+
sm2();
145+
gost28147Wrap();
146+
dstu7624Wrap();
147+
desedeWrap();
148+
rc2Wrap();
149+
}
150+
151+
public static void main(String[] args)
152+
{
153+
runTest(new ShortWrapCipherTextTest());
154+
}
155+
}

‎docs/releasenotes.html‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ <h3>2.1.3 Additional Features and Functionality</h3>
5252
<li>The SM9-KEM is now also available through the JDK 21 javax.crypto.KEM API as KEM.SM9-KEM: the encapsulator takes a recipient public key from SM9EncMasterPublicKey.getUserPublicKey(id), the decapsulator the matching user private key. By default (a null spec, or KTSParameterSpec.Builder.withNoKdf()) the shared secret is the mechanism's own GM/T 0044.4 KDF output at the requested size - the interoperable form; a KTSParameterSpec KDF may optionally be layered on top for generic use, in which case the GM/T 0044.4 KDF first produces a 256-bit shared secret for the configured KDF (note this form does not interoperate with other SM9 implementations). The SM9 private keys (the encryption master private key and users' decapsulation keys) now also honour the JCA javax.security.auth.Destroyable contract: destroy() invalidates the underlying key material - dropping the immutable secret values and zeroizing the carried identity - after which isDestroyed() reflects the state and the secret-bearing operations (getEncoded(), generateUserKeyPair(), decapsulation) throw IllegalStateException; the published master public key, and so the sender side of the scheme, is unaffected.</li>
5353
<li>Added SM9 identity-based public-key encryption (GM/T 0044.4-2016), exposed through the BouncyCastle provider as Cipher.SM9. The lightweight implementation is SM9Engine in org.bouncycastle.crypto.engines, reusing the encryption master/user key infrastructure of the SM9 KEM; a sender encrypts to the recipient's public key, formed from the published master public key and the recipient's identity via SM9EncMasterPublicKey.getUserPublicKey(id) - the same recipient key the SM9-KEM encapsulates to - and the recipient decrypts with the private key derived for its identity via SM9EncMasterPrivateKey.generateUserKeyPair(id). GM/T 0044.4 defines two data-encapsulation modes: the default wraps the message with the SM4 block cipher (Cipher.SM9), while SM9/XOR/NoPadding uses the KDF as a stream cipher; the ciphertext is emitted as the GM/T 0080-2020 SM9Cipher structure (enType, C1, C3, C2) and is self-describing, so a single Cipher.SM9 decrypts either mode. Verified against the official GM/T 0044.5-2016 Annex D worked example.</li>
5454
<li>Added the SM9 identity-based digital signature algorithm (GM/T 0044.2-2016), exposed through the BouncyCastle provider as Signature.SM9. The lightweight implementation is SM9Signer in org.bouncycastle.crypto.signers, with the signature master key-pair generator in org.bouncycastle.crypto.generators and the key parameter classes in org.bouncycastle.crypto.params. The master key pair comes from KeyPairGenerator.SM9-SIGN and round-trips through KeyFactory.SM9; a user's key pair is derived from the master private key via the SM9SignMasterPrivateKey.generateUserKeyPair(id) interface method (the deterministic KGC extraction, hid = 0x01), and a verifier forms the signer's public key from the published master public key and the signer's identity alone via SM9SignMasterPublicKey.getUserPublicKey(id) - no AlgorithmParameterSpec is involved, matching the SM9 encryption side. The signature private keys honour the Destroyable contract in the same way as the SM9 encryption keys, and the JCA signature is the GM/T 0080-2020 SM9Signature structure (DER: h, S). Verified against the official GM/T 0044.5-2016 Annex A worked example, including zero-length messages.</li>
55+
<li>SM2Engine.decrypt and the GOST28147, DSTU7624, DESede and RC2 key-wrap engines sized their output as new byte[inLen - overhead] without first checking the ciphertext was at least that overhead, so a short attacker-supplied ciphertext or wrapped key threw NegativeArraySizeException / ArrayIndexOutOfBoundsException instead of the declared InvalidCipherTextException. The engines now reject an under-length input with InvalidCipherTextException, matching the guard the IES, RFC 3394 and RFC 5649 engines already have.</li>
5556
</ul>
5657

5758
<a id="r1rv85"><h3>2.2.1 Version</h3></a>

0 commit comments

Comments
 (0)