Skip to content

Commit 4092ede

Browse files
committed
added additional checking to XMSS BDS tree parsing. Failures now mostly cause IOException
1 parent 0a702f1 commit 4092ede

File tree

10 files changed

+97
-66
lines changed

10 files changed

+97
-66
lines changed

core/src/main/java/org/bouncycastle/pqc/crypto/gmss/GMSSKeyPairGenerator.java

+10-17
Original file line numberDiff line numberDiff line change
@@ -178,26 +178,19 @@ private AsymmetricCipherKeyPair genKeyPair()
178178
// from bottom up to the root
179179
for (int h = numLayer - 1; h >= 0; h--)
180180
{
181-
GMSSRootCalc tree = new GMSSRootCalc(this.heightOfTrees[h], this.K[h], digestProvider);
182-
try
183-
{
184-
// on lowest layer no lower root is available, so just call
185-
// the method with null as first parameter
186-
if (h == numLayer - 1)
187-
{
188-
tree = this.generateCurrentAuthpathAndRoot(null, currentStack[h], seeds[h], h);
189-
}
190-
else
191-
// otherwise call the method with the former computed root
192-
// value
193-
{
194-
tree = this.generateCurrentAuthpathAndRoot(currentRoots[h + 1], currentStack[h], seeds[h], h);
195-
}
181+
GMSSRootCalc tree;
196182

183+
// on lowest layer no lower root is available, so just call
184+
// the method with null as first parameter
185+
if (h == numLayer - 1)
186+
{
187+
tree = this.generateCurrentAuthpathAndRoot(null, currentStack[h], seeds[h], h);
197188
}
198-
catch (Exception e1)
189+
else
190+
// otherwise call the method with the former computed root
191+
// value
199192
{
200-
e1.printStackTrace();
193+
tree = this.generateCurrentAuthpathAndRoot(currentRoots[h + 1], currentStack[h], seeds[h], h);
201194
}
202195

203196
// set initial values needed for the private key construction

core/src/main/java/org/bouncycastle/pqc/crypto/rainbow/RainbowParameters.java

+5-12
Original file line numberDiff line numberDiff line change
@@ -44,37 +44,30 @@ public RainbowParameters()
4444
public RainbowParameters(int[] vi)
4545
{
4646
this.vi = vi;
47-
try
48-
{
49-
checkParams();
50-
}
51-
catch (Exception e)
52-
{
53-
e.printStackTrace();
54-
}
47+
48+
checkParams();
5549
}
5650

5751
private void checkParams()
58-
throws Exception
5952
{
6053
if (vi == null)
6154
{
62-
throw new Exception("no layers defined.");
55+
throw new IllegalArgumentException("no layers defined.");
6356
}
6457
if (vi.length > 1)
6558
{
6659
for (int i = 0; i < vi.length - 1; i++)
6760
{
6861
if (vi[i] >= vi[i + 1])
6962
{
70-
throw new Exception(
63+
throw new IllegalArgumentException(
7164
"v[i] has to be smaller than v[i+1]");
7265
}
7366
}
7467
}
7568
else
7669
{
77-
throw new Exception(
70+
throw new IllegalArgumentException(
7871
"Rainbow needs at least 1 layer, such that v1 < v2.");
7972
}
8073
}

core/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSMTPrivateKeyParameters.java

+8-11
Original file line numberDiff line numberDiff line change
@@ -68,21 +68,21 @@ private XMSSMTPrivateKeyParameters(Builder builder)
6868
/* import BDS state */
6969
byte[] bdsStateBinary = XMSSUtil.extractBytesAtOffset(privateKey, position, privateKey.length - position);
7070

71-
BDSStateMap bdsImport = null;
7271
try
7372
{
74-
bdsImport = (BDSStateMap)XMSSUtil.deserialize(bdsStateBinary);
73+
BDSStateMap bdsImport = (BDSStateMap)XMSSUtil.deserialize(bdsStateBinary, BDSStateMap.class);
74+
75+
bdsImport.setXMSS(builder.xmss);
76+
bdsState = bdsImport;
7577
}
7678
catch (IOException e)
7779
{
78-
e.printStackTrace();
80+
throw new IllegalArgumentException(e.getMessage(), e);
7981
}
8082
catch (ClassNotFoundException e)
8183
{
82-
e.printStackTrace();
84+
throw new IllegalArgumentException(e.getMessage(), e);
8385
}
84-
bdsImport.setXMSS(builder.xmss);
85-
bdsState = bdsImport;
8686
}
8787
else
8888
{
@@ -260,17 +260,14 @@ public byte[] toByteArray()
260260
/* copy root */
261261
XMSSUtil.copyBytesAtOffset(out, root, position);
262262
/* concatenate bdsState */
263-
byte[] bdsStateOut = null;
264263
try
265264
{
266-
bdsStateOut = XMSSUtil.serialize(bdsState);
265+
return Arrays.concatenate(out, XMSSUtil.serialize(bdsState));
267266
}
268267
catch (IOException e)
269268
{
270-
e.printStackTrace();
271-
throw new RuntimeException("error serializing bds state");
269+
throw new IllegalStateException("error serializing bds state: " + e.getMessage(), e);
272270
}
273-
return Arrays.concatenate(out, bdsStateOut);
274271
}
275272

276273
public long getIndex()

core/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSPrivateKeyParameters.java

+10-11
Original file line numberDiff line numberDiff line change
@@ -86,26 +86,25 @@ private XMSSPrivateKeyParameters(Builder builder)
8686
position += rootSize;
8787
/* import BDS state */
8888
byte[] bdsStateBinary = XMSSUtil.extractBytesAtOffset(privateKey, position, privateKey.length - position);
89-
BDS bdsImport = null;
9089
try
9190
{
92-
bdsImport = (BDS)XMSSUtil.deserialize(bdsStateBinary);
91+
BDS bdsImport = (BDS)XMSSUtil.deserialize(bdsStateBinary, BDS.class);
92+
bdsImport.setXMSS(builder.xmss);
93+
bdsImport.validate();
94+
if (bdsImport.getIndex() != index)
95+
{
96+
throw new IllegalStateException("serialized BDS has wrong index");
97+
}
98+
bdsState = bdsImport;
9399
}
94100
catch (IOException e)
95101
{
96-
e.printStackTrace();
102+
throw new IllegalArgumentException(e.getMessage(), e);
97103
}
98104
catch (ClassNotFoundException e)
99105
{
100-
e.printStackTrace();
101-
}
102-
bdsImport.setXMSS(builder.xmss);
103-
bdsImport.validate();
104-
if (bdsImport.getIndex() != index)
105-
{
106-
throw new IllegalStateException("serialized BDS has wrong index");
106+
throw new IllegalArgumentException(e.getMessage(), e);
107107
}
108-
bdsState = bdsImport;
109108
}
110109
else
111110
{

core/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSUtil.java

+15-2
Original file line numberDiff line numberDiff line change
@@ -321,12 +321,25 @@ public static byte[] serialize(Object obj)
321321
return out.toByteArray();
322322
}
323323

324-
public static Object deserialize(byte[] data)
324+
public static Object deserialize(byte[] data, Class clazz)
325325
throws IOException, ClassNotFoundException
326326
{
327327
ByteArrayInputStream in = new ByteArrayInputStream(data);
328328
ObjectInputStream is = new ObjectInputStream(in);
329-
return is.readObject();
329+
Object obj = is.readObject();
330+
331+
if (is.available() != 0)
332+
{
333+
throw new IOException("unexpected data found at end of ObjectInputStream");
334+
}
335+
if (clazz.isInstance(obj))
336+
{
337+
return obj;
338+
}
339+
else
340+
{
341+
throw new IOException("unexpected class found in ObjectInputStream");
342+
}
330343
}
331344

332345
public static int calculateTau(int index, int height)

core/src/main/java/org/bouncycastle/pqc/math/linearalgebra/GF2nField.java

+3-10
Original file line numberDiff line numberDiff line change
@@ -155,16 +155,9 @@ protected final GF2Polynomial[] invertMatrix(GF2Polynomial[] matrix)
155155
// initialize a as a copy of matrix and inv as E(inheitsmatrix)
156156
for (i = 0; i < mDegree; i++)
157157
{
158-
try
159-
{
160-
a[i] = new GF2Polynomial(matrix[i]);
161-
inv[i] = new GF2Polynomial(mDegree);
162-
inv[i].setBit(mDegree - 1 - i);
163-
}
164-
catch (RuntimeException BDNEExc)
165-
{
166-
BDNEExc.printStackTrace();
167-
}
158+
a[i] = new GF2Polynomial(matrix[i]);
159+
inv[i] = new GF2Polynomial(mDegree);
160+
inv[i].setBit(mDegree - 1 - i);
168161
}
169162
// construct triangle matrix so that for each a[i] the first i bits are
170163
// zero

core/src/test/java/org/bouncycastle/pqc/crypto/test/XMSSMTPrivateKeyTest.java

+43-1
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,61 @@
55

66
import junit.framework.TestCase;
77
import org.bouncycastle.crypto.digests.SHA256Digest;
8+
import org.bouncycastle.pqc.crypto.xmss.XMSS;
89
import org.bouncycastle.pqc.crypto.xmss.XMSSMT;
910
import org.bouncycastle.pqc.crypto.xmss.XMSSMTParameters;
11+
import org.bouncycastle.pqc.crypto.xmss.XMSSParameters;
12+
import org.bouncycastle.pqc.crypto.xmss.XMSSPrivateKeyParameters;
1013
import org.bouncycastle.util.Arrays;
14+
import org.bouncycastle.util.encoders.Base64;
1115

1216
/**
1317
* Test cases for XMSSMTPrivateKey class.
1418
*/
1519
public class XMSSMTPrivateKeyTest
1620
extends TestCase
1721
{
22+
public void testPrivateKeySerialisation()
23+
throws Exception
24+
{
25+
String stream = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAArO0ABXNyACJzdW4ucm1pLnNlcnZlci5BY3RpdmF0aW9uR3JvdXBJbXBsT+r9SAwuMqcCAARaAA1ncm91cEluYWN0aXZlTAAGYWN0aXZldAAVTGphdmEvdXRpbC9IYXNodGFibGU7TAAHZ3JvdXBJRHQAJ0xqYXZhL3JtaS9hY3RpdmF0aW9uL0FjdGl2YXRpb25Hcm91cElEO0wACWxvY2tlZElEc3QAEExqYXZhL3V0aWwvTGlzdDt4cgAjamF2YS5ybWkuYWN0aXZhdGlvbi5BY3RpdmF0aW9uR3JvdXCVLvKwBSnVVAIAA0oAC2luY2FybmF0aW9uTAAHZ3JvdXBJRHEAfgACTAAHbW9uaXRvcnQAJ0xqYXZhL3JtaS9hY3RpdmF0aW9uL0FjdGl2YXRpb25Nb25pdG9yO3hyACNqYXZhLnJtaS5zZXJ2ZXIuVW5pY2FzdFJlbW90ZU9iamVjdEUJEhX14n4xAgADSQAEcG9ydEwAA2NzZnQAKExqYXZhL3JtaS9zZXJ2ZXIvUk1JQ2xpZW50U29ja2V0RmFjdG9yeTtMAANzc2Z0AChMamF2YS9ybWkvc2VydmVyL1JNSVNlcnZlclNvY2tldEZhY3Rvcnk7eHIAHGphdmEucm1pLnNlcnZlci5SZW1vdGVTZXJ2ZXLHGQcSaPM5+wIAAHhyABxqYXZhLnJtaS5zZXJ2ZXIuUmVtb3RlT2JqZWN002G0kQxhMx4DAAB4cHcSABBVbmljYXN0U2VydmVyUmVmeAAAFbNwcAAAAAAAAAAAcHAAcHBw";
26+
27+
XMSSParameters params = new XMSSParameters(10, new SHA256Digest());
28+
29+
byte[] output = Base64.decode(new String(stream).getBytes("UTF-8"));
30+
31+
32+
//Simple Exploit
33+
34+
try
35+
{
36+
new XMSSPrivateKeyParameters.Builder(params).withPrivateKey(output, params).build();
37+
}
38+
catch (IllegalArgumentException e)
39+
{
40+
assertTrue(e.getCause() instanceof IOException);
41+
}
42+
43+
//Same Exploit other method
44+
45+
XMSS xmss2 = new XMSS(params, new SecureRandom());
46+
47+
xmss2.generateKeys();
48+
49+
byte[] publicKey = xmss2.exportPublicKey();
50+
51+
try
52+
{
53+
xmss2.importState(output, publicKey);
54+
}
55+
catch (IllegalArgumentException e)
56+
{
57+
assertTrue(e.getCause() instanceof IOException);
58+
}
59+
}
1860

1961
public void testPrivateKeyParsingSHA256()
20-
throws IOException, ClassNotFoundException
62+
throws Exception
2163
{
2264
XMSSMTParameters params = new XMSSMTParameters(20, 10, new SHA256Digest());
2365
XMSSMT mt = new XMSSMT(params, new SecureRandom());

docs/releasenotes.html

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ <h3>2.1.1 Version</h3>
2828
<h3>2.1.2 Defects Fixed</h3>
2929
<ul>
3030
<li>Base64/UrlBase64 would throw an exception on a zero length string. This has been fixed.</li>
31+
<li>XMSS applies further validation to deserialisation of the BDS tree so that failure occurs as soon as tampering is detected.</li>
3132
</ul>
3233
<h3>2.1.3 Additional Features and Functionality</h3>
3334
<ul>

prov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/BCXMSSMTPrivateKey.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public BCXMSSMTPrivateKey(PrivateKeyInfo keyInfo)
5252

5353
if (xmssMtPrivateKey.getBdsState() != null)
5454
{
55-
keyBuilder.withBDSState((BDSStateMap)XMSSUtil.deserialize(xmssMtPrivateKey.getBdsState()));
55+
keyBuilder.withBDSState((BDSStateMap)XMSSUtil.deserialize(xmssMtPrivateKey.getBdsState(), BDSStateMap.class));
5656
}
5757

5858
this.keyParams = keyBuilder.build();

prov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/BCXMSSPrivateKey.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public BCXMSSPrivateKey(PrivateKeyInfo keyInfo)
5151

5252
if (xmssPrivateKey.getBdsState() != null)
5353
{
54-
keyBuilder.withBDSState((BDS)XMSSUtil.deserialize(xmssPrivateKey.getBdsState()));
54+
keyBuilder.withBDSState((BDS)XMSSUtil.deserialize(xmssPrivateKey.getBdsState(), BDS.class));
5555
}
5656

5757
this.keyParams = keyBuilder.build();

0 commit comments

Comments
 (0)