Skip to content

Commit 215a358

Browse files
committed
LMS: LMSPrivateKeyParameters drops the PlaceholderLMSPrivateKey subclass - a placeholder is now a key with no node cache, and checkUsable (destroyed or placeholder) guards every entry point in place of two overrides; the cache is sized once in createCache and maxCacheR folds into tCache.length, so a shard caches the same 63 nodes as its parent rather than 31 at h = 5, and a key derived from a destroyed parent inherits the flag along with the zeroed seed
1 parent cc6ba6c commit 215a358

1 file changed

Lines changed: 63 additions & 53 deletions

File tree

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

Lines changed: 63 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public class LMSPrivateKeyParameters
4646
// Two tiers of Merkle tree nodes are kept, neither of them secret: every node is published in some
4747
// signature or recomputed by every verifier.
4848
//
49-
// tCache holds nodes 1 .. maxCacheR - 1 (at most 63, about 2 KB), computed on demand and kept for the
49+
// tCache holds nodes 1 .. tCache.length - 1 (at most 63, about 2 KB), computed on demand and kept for the
5050
// life of the key. It is the tier the encoding persists, so a decoded key resumes with it. Caching
5151
// deeper nodes buys nothing that lasts: each leaf is consumed once by its parent, and an unbounded
5252
// cache of every node reaches 2 GB at h = 25. (The WeakHashMap this replaced keyed the deeper nodes
@@ -66,7 +66,6 @@ public class LMSPrivateKeyParameters
6666
// modified or wiped.
6767
//
6868
private final byte[][] tCache;
69-
private final int maxCacheR;
7069
private RetainedPath retained;
7170

7271
/**
@@ -127,7 +126,18 @@ public static LMSPrivateKeyParameters generate(LMSParameters lmsParameters, Secu
127126
*/
128127
static LMSPrivateKeyParameters createPlaceholder(LMSParameters lmsParameters, int maxQ)
129128
{
130-
return new PlaceholderLMSPrivateKey(lmsParameters, maxQ);
129+
return new LMSPrivateKeyParameters(lmsParameters, maxQ);
130+
}
131+
132+
/**
133+
* The node cache for a tree of the given height: room for nodes 1 .. length - 1, where length is
134+
* the whole tree (2^(h + 1) nodes) or CACHE_TOP_LIMIT, whichever is smaller.
135+
*/
136+
private static byte[][] createCache(LMSigParameters sigParameters)
137+
{
138+
int length = Math.min(CACHE_TOP_LIMIT, 1 << (sigParameters.getH() + 1));
139+
140+
return new byte[length][];
131141
}
132142

133143
/**
@@ -174,16 +184,15 @@ public LMSPrivateKeyParameters(LMSigParameters sigParameters, LMOtsParameters ot
174184
this.I = I;
175185
this.maxQ = maxQ;
176186
this.masterSecret = masterSecret;
177-
this.maxCacheR = Math.min(CACHE_TOP_LIMIT, 1 << (sigParameters.getH() + 1));
178-
this.tCache = new byte[maxCacheR][];
187+
this.tCache = createCache(sigParameters);
179188
}
180189

181190
/**
182191
* A key with no position, identifier or seed of its own - the placeholder an HSS hierarchy is
183192
* built with for the levels below the root, each of which resetKeyToIndex replaces from the
184193
* level above before the key is used. The sentinel values are deliberately ones the public
185194
* constructor refuses, so a placeholder can never be mistaken for a key that was merely built
186-
* carelessly; a subclass using this must not present the result as a usable key.
195+
* carelessly, and the key itself refuses to sign, publish, encode or derive from itself.
187196
*
188197
* @deprecated This class is not intended to be subclassed; the constructor will be removed.
189198
*/
@@ -204,27 +213,26 @@ private LMSPrivateKeyParameters(LMSParameters lmsParameters, int maxQ)
204213
this.I = new byte[0];
205214
this.maxQ = maxQ;
206215
this.masterSecret = new byte[0];
207-
this.maxCacheR = Math.min(CACHE_TOP_LIMIT, 1 << (lmsParameters.getLMSigParam().getH() + 1));
208-
this.tCache = new byte[maxCacheR][];
209-
}
210-
211-
private LMSPrivateKeyParameters(LMSPrivateKeyParameters parent, int q, int maxQ)
212-
{
213-
this(parent, q, maxQ, Math.min(CACHE_TOP_LIMIT, 1 << parent.lmsParameters.getLMSigParam().getH()));
216+
// No tree to cache: resetKeyToIndex replaces a placeholder before anything reaches its nodes. The
217+
// absent cache is also what marks the key as a placeholder (checkNotPlaceholder), so one that leaked
218+
// past the reset fails at its first use rather than encoding a bogus key.
219+
this.tCache = null;
214220
}
215221

216222
// Called under the parent's lock (extractKeyShard, repositionTo), which is what makes reading its
217-
// retained path safe. I, masterSecret and tCache are shared by reference with the parent; the retained
218-
// path too, but it is immutable and holds no secrets.
219-
private LMSPrivateKeyParameters(LMSPrivateKeyParameters parent, int q, int maxQ, int maxCacheR)
223+
// retained path and destroyed flag safe. I, masterSecret and tCache are shared by reference with the
224+
// parent; the retained path too, but it is immutable and holds no secrets.
225+
private LMSPrivateKeyParameters(LMSPrivateKeyParameters parent, int q, int maxQ)
220226
{
221227
super(true);
222228
this.lmsParameters = parent.lmsParameters;
223229
this.q = q;
224230
this.I = parent.I;
225231
this.maxQ = maxQ;
226232
this.masterSecret = parent.masterSecret;
227-
this.maxCacheR = maxCacheR;
233+
// the flag travels with the array it describes: a copy of a destroyed key must not present the
234+
// zeroed seed as a live one
235+
this.destroyed = parent.destroyed;
228236
this.tCache = parent.tCache;
229237
this.retained = parent.retained;
230238
this.publicKey = parent.publicKey;
@@ -254,7 +262,7 @@ synchronized LMSPrivateKeyParameters repositionTo(int q)
254262
"LMS private key q out of range: q=" + q + " 2^h=" + twoToH);
255263
}
256264

257-
return new LMSPrivateKeyParameters(this, q, twoToH, maxCacheR);
265+
return new LMSPrivateKeyParameters(this, q, twoToH);
258266
}
259267

260268
public static LMSPrivateKeyParameters getInstance(byte[] privEnc, byte[] pubEnc)
@@ -519,7 +527,7 @@ byte[][] deriveChildKey()
519527
{
520528
synchronized (this)
521529
{
522-
checkDestroyed();
530+
checkUsable();
523531

524532
if (q >= maxQ)
525533
{
@@ -548,7 +556,7 @@ byte[][] deriveChildKey(int q)
548556

549557
synchronized (this)
550558
{
551-
checkDestroyed();
559+
checkUsable();
552560

553561
return LMSEngine.deriveChildKey(lmsParameters.getLMOTSParam(), I, masterSecret, q);
554562
}
@@ -563,6 +571,7 @@ boolean hasIdentity(byte[] I, byte[] masterSecret)
563571
{
564572
synchronized (this)
565573
{
574+
// not checkUsable: resetKeyToIndex asks a placeholder this, and its empty I answers no
566575
checkDestroyed();
567576

568577
return Arrays.areEqual(this.I, I) && Arrays.constantTimeAreEqual(this.masterSecret, masterSecret);
@@ -586,10 +595,6 @@ synchronized void incIndex()
586595

587596
public LMSContext generateLMSContext()
588597
{
589-
// Step 1.
590-
LMSigParameters sigParameters = this.getSigParameters();
591-
592-
// Step 2
593598
int q;
594599
byte[][] path;
595600

@@ -600,7 +605,7 @@ public LMSContext generateLMSContext()
600605
//
601606
synchronized (this)
602607
{
603-
checkDestroyed();
608+
checkUsable();
604609

605610
if (this.q >= maxQ)
606611
{
@@ -610,7 +615,7 @@ public LMSContext generateLMSContext()
610615
path = advanceRetainedPath(q);
611616
}
612617

613-
return LMSEngine.generateSignContext(sigParameters, lmsParameters.getLMOTSParam(), I, q, masterSecret, path);
618+
return LMSEngine.generateSignContext(getSigParameters(), getOtsParameters(), I, q, masterSecret, path);
614619
}
615620

616621
public byte[] generateSignature(LMSContext context)
@@ -691,7 +696,7 @@ public byte[] getMasterSecret()
691696

692697
// clone first, check second: a destroy() that lands in between has set the flag before
693698
// it clears the array, so a stale copy is never handed out.
694-
checkDestroyed();
699+
checkUsable();
695700

696701
return rv;
697702
}
@@ -722,6 +727,16 @@ public boolean isDestroyed()
722727
return destroyed;
723728
}
724729

730+
/**
731+
* Refuses a key that cannot act as one: a destroyed key, or the placeholder an HSS hierarchy holds for
732+
* a level not yet built (marked by having no node cache).
733+
*/
734+
private void checkUsable()
735+
{
736+
checkDestroyed();
737+
checkNotPlaceholder();
738+
}
739+
725740
private void checkDestroyed()
726741
{
727742
if (destroyed)
@@ -730,6 +745,14 @@ private void checkDestroyed()
730745
}
731746
}
732747

748+
private void checkNotPlaceholder()
749+
{
750+
if (tCache == null)
751+
{
752+
throw new IllegalStateException("placeholder only");
753+
}
754+
}
755+
733756
public int getIndexLimit()
734757
{
735758
return maxQ;
@@ -757,6 +780,9 @@ public LMSPublicKeyParameters getPublicKey()
757780
LMSPublicKeyParameters pk = publicKey;
758781
if (pk == null)
759782
{
783+
// not checkUsable: a destroyed key still publishes its root where it is cached
784+
checkNotPlaceholder();
785+
760786
retainFirstPath();
761787

762788
// Tree nodes and I are immutable once published, so the public key shares them rather than copying.
@@ -813,7 +839,7 @@ synchronized boolean isPathRetained()
813839

814840
byte[] findT(int r)
815841
{
816-
if (r >= maxCacheR)
842+
if (r >= tCache.length)
817843
{
818844
return calcT(r);
819845
}
@@ -906,7 +932,7 @@ private byte[][] advanceRetainedPath(int q)
906932
for (int i = 0; i < fresh; ++i)
907933
{
908934
int node = r >> i;
909-
if (node < maxCacheR && tCache[node] == null)
935+
if (node < tCache.length && tCache[node] == null)
910936
{
911937
tCache[node] = anc[i];
912938
}
@@ -945,9 +971,10 @@ private byte[] calcT(int r)
945971
// These can be pre generated at the time of key generation and held within the private key.
946972
// However it will cost memory to have them stick around.
947973
//
948-
checkDestroyed();
949974

950-
return LMSEngine.computeLeaf(tDigest, lmsParameters.getLMOTSParam(), I, r, r - twoToh, masterSecret);
975+
checkUsable();
976+
977+
return LMSEngine.computeLeaf(tDigest, getOtsParameters(), I, r, r - twoToh, masterSecret);
951978
}
952979

953980
byte[] t2r = findT(2 * r);
@@ -967,7 +994,9 @@ void primeTreeCache(byte[][] cachedT)
967994
{
968995
synchronized (tCache)
969996
{
970-
for (int r = 1; r < cachedT.length && r < tCache.length; r++)
997+
int limit = Math.min(cachedT.length, tCache.length);
998+
999+
for (int r = 1; r < limit; r++)
9711000
{
9721001
if (cachedT[r] != null)
9731002
{
@@ -1048,7 +1077,7 @@ public int hashCode()
10481077
public byte[] getEncoded()
10491078
throws IOException
10501079
{
1051-
checkDestroyed();
1080+
checkUsable();
10521081

10531082
int q = getIndex();
10541083

@@ -1080,7 +1109,7 @@ public byte[] getEncoded()
10801109

10811110
// The whole of the in-memory cache is eligible, so a decoded key resumes with the cache it was
10821111
// encoded with; findT computes any node not yet there.
1083-
int cacheTop = maxCacheR;
1112+
int cacheTop = tCache.length;
10841113

10851114
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
10861115

@@ -1101,23 +1130,4 @@ public byte[] getEncoded()
11011130

11021131
return bOut.toByteArray();
11031132
}
1104-
1105-
private static class PlaceholderLMSPrivateKey
1106-
extends LMSPrivateKeyParameters
1107-
{
1108-
PlaceholderLMSPrivateKey(LMSParameters lmsParameters, int maxQ)
1109-
{
1110-
super(lmsParameters, maxQ);
1111-
}
1112-
1113-
public LMSContext generateLMSContext()
1114-
{
1115-
throw new RuntimeException("placeholder only");
1116-
}
1117-
1118-
public LMSPublicKeyParameters getPublicKey()
1119-
{
1120-
throw new RuntimeException("placeholder only");
1121-
}
1122-
}
11231133
}

0 commit comments

Comments
 (0)