Skip to content

Commit a3ebe57

Browse files
suzannajiwanicopybara-github
authored andcommitted
Add InMemoryLruCache ChallengeChecker implementation
PiperOrigin-RevId: 780246310
1 parent 2b6c44b commit a3ebe57

File tree

2 files changed

+107
-0
lines changed

2 files changed

+107
-0
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.android.keyattestation.verifier.challengecheckers
2+
3+
import com.android.keyattestation.verifier.ChallengeChecker
4+
import com.google.protobuf.ByteString
5+
6+
/**
7+
* A [ChallengeChecker] which checks for replay of challenges via an in-memory LRU cache which holds
8+
* up to `maxCacheSize` challenges. Challenges are considered invalid if they are already present in
9+
* the cache, which prevents replay (reuse of challenges). Checking a challenge will affect the
10+
* ordering of the cache, making it more-recently-used.
11+
*
12+
* @property maxCacheSize the maximum number of challenges to cache
13+
*/
14+
class InMemoryLruCache(private val maxCacheSize: Int) : ChallengeChecker {
15+
// Use a LinkedHashMap instead of LinkedHashSet even though we don't care about the values since
16+
// it can order entries by access-order. Use default initial capacity and load factor.
17+
private val cache: LinkedHashMap<ByteString, Int> =
18+
object : LinkedHashMap<ByteString, Int>(16, 0.75f, true) {
19+
20+
// Used to query whether the oldest entry should be removed from the cache.
21+
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<ByteString, Int>) =
22+
size > maxCacheSize
23+
}
24+
25+
override fun checkChallenge(challenge: ByteString): Boolean {
26+
val previousValue = cache.putIfAbsent(challenge, 1)
27+
return previousValue == null
28+
}
29+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package com.android.keyattestation.verifier.challengecheckers
2+
3+
import com.google.common.truth.Truth.assertThat
4+
import com.google.protobuf.ByteString
5+
import org.junit.Test
6+
import org.junit.runner.RunWith
7+
import org.junit.runners.JUnit4
8+
9+
@RunWith(JUnit4::class)
10+
class InMemoryLruCacheTest {
11+
12+
@Test
13+
fun checkChallenge_firstChallenge_returnsTrue() {
14+
val challengeChecker = InMemoryLruCache(1)
15+
assertThat(challengeChecker.checkChallenge(ByteString.copyFromUtf8("challenge"))).isTrue()
16+
}
17+
18+
@Test
19+
fun checkChallenge_partialCacheCheckNewChallenge_returnsTrue() {
20+
val challengeChecker = InMemoryLruCache(10)
21+
for (i in 1..9) {
22+
assertThat(challengeChecker.checkChallenge(ByteString.copyFromUtf8("challenge$i"))).isTrue()
23+
}
24+
25+
assertThat(challengeChecker.checkChallenge(ByteString.copyFromUtf8("foo"))).isTrue()
26+
}
27+
28+
@Test
29+
fun checkChallenge_fullCacheCheckExistingChallenge_returnsFalse() {
30+
val challengeChecker = InMemoryLruCache(10)
31+
for (i in 1..10) {
32+
assertThat(challengeChecker.checkChallenge(ByteString.copyFromUtf8("challenge$i"))).isTrue()
33+
}
34+
35+
assertThat(challengeChecker.checkChallenge(ByteString.copyFromUtf8("challenge1"))).isFalse()
36+
}
37+
38+
@Test
39+
fun checkChallenge_overflowCacheCheckOldestChallenge_returnsTrue() {
40+
val challengeChecker = InMemoryLruCache(10)
41+
42+
// Fill cache with 10 challenges and overflow with the 11th challenge.
43+
for (i in 1..11) {
44+
assertThat(challengeChecker.checkChallenge(ByteString.copyFromUtf8("challenge$i"))).isTrue()
45+
}
46+
47+
assertThat(challengeChecker.checkChallenge(ByteString.copyFromUtf8("challenge1"))).isTrue()
48+
}
49+
50+
@Test
51+
fun checkChallenge_overflowCacheCheckNewerChallenge_returnsFalse() {
52+
val challengeChecker = InMemoryLruCache(10)
53+
for (i in 1..11) {
54+
assertThat(challengeChecker.checkChallenge(ByteString.copyFromUtf8("challenge$i"))).isTrue()
55+
}
56+
57+
assertThat(challengeChecker.checkChallenge(ByteString.copyFromUtf8("challenge2"))).isFalse()
58+
}
59+
60+
@Test
61+
fun checkChallenge_checkingChallenge_affectsCacheOrder() {
62+
// fill cache
63+
val challengeChecker = InMemoryLruCache(3)
64+
for (i in 1..3) {
65+
assertThat(challengeChecker.checkChallenge(ByteString.copyFromUtf8("challenge$i"))).isTrue()
66+
}
67+
68+
// check oldest challenge
69+
assertThat(challengeChecker.checkChallenge(ByteString.copyFromUtf8("challenge1"))).isFalse()
70+
71+
// add new challenge to overflow cache + kick out least-recently-used challenge
72+
assertThat(challengeChecker.checkChallenge(ByteString.copyFromUtf8("challenge4"))).isTrue()
73+
74+
// check that challenge1 is still in the cache + challenge2 is kicked out
75+
assertThat(challengeChecker.checkChallenge(ByteString.copyFromUtf8("challenge1"))).isFalse()
76+
assertThat(challengeChecker.checkChallenge(ByteString.copyFromUtf8("challenge2"))).isTrue()
77+
}
78+
}

0 commit comments

Comments
 (0)