1+ package com.auth0.android.dpop
2+
3+ import android.content.Context
4+ import android.content.pm.PackageManager
5+ import android.os.Build
6+ import android.security.keystore.KeyGenParameterSpec
7+ import android.security.keystore.KeyProperties
8+ import android.util.Log
9+ import com.nhaarman.mockitokotlin2.any
10+ import com.nhaarman.mockitokotlin2.anyOrNull
11+ import com.nhaarman.mockitokotlin2.mock
12+ import com.nhaarman.mockitokotlin2.never
13+ import com.nhaarman.mockitokotlin2.verify
14+ import com.nhaarman.mockitokotlin2.whenever
15+ import org.hamcrest.MatcherAssert.assertThat
16+ import org.hamcrest.Matchers.`is`
17+ import org.hamcrest.Matchers.notNullValue
18+ import org.hamcrest.Matchers.nullValue
19+ import org.junit.Assert.assertEquals
20+ import org.junit.Assert.assertThrows
21+ import org.junit.Before
22+ import org.junit.Test
23+ import org.junit.runner.RunWith
24+ import org.mockito.Mockito.doNothing
25+ import org.powermock.api.mockito.PowerMockito
26+ import org.powermock.core.classloader.annotations.PrepareForTest
27+ import org.powermock.modules.junit4.PowerMockRunner
28+ import org.powermock.reflect.Whitebox
29+ import java.security.InvalidAlgorithmParameterException
30+ import java.security.KeyPairGenerator
31+ import java.security.KeyStore
32+ import java.security.KeyStoreException
33+ import java.security.PrivateKey
34+ import java.security.PublicKey
35+ import java.security.cert.Certificate
36+ import javax.security.auth.x500.X500Principal
37+
38+ @RunWith(PowerMockRunner ::class )
39+ @PrepareForTest(
40+ DPoPKeyStore ::class ,
41+ KeyStore ::class ,
42+ KeyPairGenerator ::class ,
43+ KeyGenParameterSpec .Builder ::class ,
44+ Build .VERSION ::class ,
45+ X500Principal ::class ,
46+ Log ::class
47+ )
48+ public class DPoPKeyStoreTest {
49+
50+ private lateinit var mockKeyStore: KeyStore
51+ private lateinit var mockKeyPairGenerator: KeyPairGenerator
52+ private lateinit var mockContext: Context
53+ private lateinit var mockPackageManager: PackageManager
54+ private lateinit var mockSpecBuilder: KeyGenParameterSpec .Builder
55+
56+ private lateinit var dpopKeyStore: DPoPKeyStore
57+
58+ @Before
59+ public fun setUp () {
60+
61+ mockKeyStore = mock()
62+ mockKeyPairGenerator = mock()
63+ mockContext = mock()
64+ mockPackageManager = mock()
65+ mockSpecBuilder = mock()
66+
67+ PowerMockito .mockStatic(KeyStore ::class .java)
68+ PowerMockito .mockStatic(KeyPairGenerator ::class .java)
69+ PowerMockito .mockStatic(Log ::class .java)
70+ PowerMockito .mockStatic(Build .VERSION ::class .java)
71+ Whitebox .setInternalState(Build .VERSION ::class .java, " SDK_INT" , Build .VERSION_CODES .P )
72+
73+ PowerMockito .whenNew(KeyGenParameterSpec .Builder ::class .java).withAnyArguments()
74+ .thenReturn(mockSpecBuilder)
75+
76+ // Configure mocks
77+ PowerMockito .`when `(KeyStore .getInstance(" AndroidKeyStore" )).thenReturn(mockKeyStore)
78+ doNothing().whenever(mockKeyStore).load(anyOrNull())
79+ PowerMockito .`when `(
80+ KeyPairGenerator .getInstance(
81+ KeyProperties .KEY_ALGORITHM_EC ,
82+ " AndroidKeyStore"
83+ )
84+ ).thenReturn(mockKeyPairGenerator)
85+
86+ whenever(mockSpecBuilder.setAlgorithmParameterSpec(any())).thenReturn(mockSpecBuilder)
87+ whenever(mockSpecBuilder.setDigests(any())).thenReturn(mockSpecBuilder)
88+ whenever(mockSpecBuilder.setCertificateSubject(any())).thenReturn(mockSpecBuilder)
89+ whenever(mockSpecBuilder.setCertificateNotBefore(any())).thenReturn(mockSpecBuilder)
90+ whenever(mockSpecBuilder.setCertificateNotAfter(any())).thenReturn(mockSpecBuilder)
91+ whenever(mockSpecBuilder.setIsStrongBoxBacked(any())).thenReturn(mockSpecBuilder)
92+ whenever(mockContext.packageManager).thenReturn(mockPackageManager)
93+ whenever(mockPackageManager.hasSystemFeature(PackageManager .FEATURE_STRONGBOX_KEYSTORE )).thenReturn(
94+ true
95+ )
96+
97+ dpopKeyStore = DPoPKeyStore (mockKeyStore)
98+ }
99+
100+ @Test
101+ public fun `generateKeyPair should generate a key pair successfully` () {
102+ whenever(mockPackageManager.hasSystemFeature(PackageManager .FEATURE_STRONGBOX_KEYSTORE )).thenReturn(
103+ false
104+ )
105+ dpopKeyStore.generateKeyPair(mockContext)
106+
107+ verify(mockKeyPairGenerator).initialize(mockSpecBuilder.build())
108+ verify(mockKeyPairGenerator).generateKeyPair()
109+ verify(mockSpecBuilder, never()).setIsStrongBoxBacked(true )
110+ }
111+
112+ @Test
113+ public fun `generateKeyPair should enable StrongBox when available` () {
114+ dpopKeyStore.generateKeyPair(mockContext)
115+ verify(mockSpecBuilder).setIsStrongBoxBacked(true )
116+ }
117+
118+ @Test
119+ public fun `generateKeyPair should throw KEY_GENERATION_ERROR when failed to generate key pair` () {
120+ val cause = InvalidAlgorithmParameterException (" Exception" )
121+ PowerMockito .`when `(
122+ mockKeyPairGenerator.initialize(mockSpecBuilder.build())
123+ ).thenThrow(cause)
124+
125+ val exception = assertThrows(DPoPException ::class .java) {
126+ dpopKeyStore.generateKeyPair(mockContext)
127+ }
128+ assertEquals(exception.message, DPoPException .KEY_GENERATION_ERROR .message)
129+ assertThat(exception.cause, `is `(cause))
130+ }
131+
132+ @Test
133+ public fun `generateKeyPair should throw UNKNOWN_ERROR when any unhandled exception occurs` () {
134+ val cause = RuntimeException (" Exception" )
135+ PowerMockito .`when `(
136+ mockKeyPairGenerator.initialize(mockSpecBuilder.build())
137+ ).thenThrow(cause)
138+
139+ val exception = assertThrows(DPoPException ::class .java) {
140+ dpopKeyStore.generateKeyPair(mockContext)
141+ }
142+ assertEquals(exception.message, DPoPException .UNKNOWN_ERROR .message)
143+ assertThat(exception.cause, `is `(cause))
144+ }
145+
146+ @Test
147+ public fun `getKeyPair should return key pair when it exists` () {
148+ val mockPrivateKey = mock<PrivateKey >()
149+ val mockPublicKey = mock<PublicKey >()
150+ val mockCertificate = mock<Certificate >()
151+
152+ whenever(mockKeyStore.getKey(any(), anyOrNull())).thenReturn(mockPrivateKey)
153+ whenever(mockKeyStore.getCertificate(any())).thenReturn(mockCertificate)
154+ whenever(mockCertificate.publicKey).thenReturn(mockPublicKey)
155+
156+ val keyPair = dpopKeyStore.getKeyPair()
157+
158+ assertThat(keyPair, `is `(notNullValue()))
159+ assertThat(keyPair!! .first, `is `(mockPrivateKey))
160+ assertThat(keyPair.second, `is `(mockPublicKey))
161+ }
162+
163+ @Test
164+ public fun `getKeyPair should return null when certificate is null` () {
165+ val mockPrivateKey = mock<PrivateKey >()
166+ whenever(mockKeyStore.getKey(any(), anyOrNull())).thenReturn(mockPrivateKey)
167+ whenever(mockKeyStore.getCertificate(any())).thenReturn(null )
168+
169+ val keyPair = dpopKeyStore.getKeyPair()
170+ assertThat(keyPair, `is `(nullValue()))
171+ }
172+
173+ @Test
174+ public fun `getKeyPair should throw KEY_STORE_ERROR on KeyStoreException` () {
175+ val cause = KeyStoreException (" Test Exception" )
176+ whenever(mockKeyStore.getKey(any(), anyOrNull())).thenThrow(cause)
177+
178+ val exception = assertThrows(DPoPException ::class .java) {
179+ dpopKeyStore.getKeyPair()
180+ }
181+ assertEquals(exception.message, DPoPException .KEY_STORE_ERROR .message)
182+ assertThat(exception.cause, `is `(cause))
183+ }
184+
185+ @Test
186+ public fun `hasKeyPair should return true when alias exists` () {
187+ whenever(mockKeyStore.containsAlias(any())).thenReturn(true )
188+ val result = dpopKeyStore.hasKeyPair()
189+ assertThat(result, `is `(true ))
190+ }
191+
192+ @Test
193+ public fun `hasKeyPair should return false when alias does not exist` () {
194+ whenever(mockKeyStore.containsAlias(any())).thenReturn(false )
195+ val result = dpopKeyStore.hasKeyPair()
196+ assertThat(result, `is `(false ))
197+ }
198+
199+ @Test
200+ public fun `hasKeyPair should throw KEY_STORE_ERROR on KeyStoreException` () {
201+ val cause = KeyStoreException (" Test Exception" )
202+ whenever(mockKeyStore.containsAlias(any())).thenThrow(cause)
203+
204+ val exception = assertThrows(DPoPException ::class .java) {
205+ dpopKeyStore.hasKeyPair()
206+ }
207+ assertEquals(exception.message, DPoPException .KEY_STORE_ERROR .message)
208+ assertThat(exception.cause, `is `(cause))
209+ }
210+
211+ @Test
212+ public fun `deleteKeyPair should call deleteEntry` () {
213+ dpopKeyStore.deleteKeyPair()
214+ verify(mockKeyStore).deleteEntry(any())
215+ }
216+
217+ @Test
218+ public fun `deleteKeyPair should throw KEY_STORE_ERROR on KeyStoreException` () {
219+ val cause = KeyStoreException (" Test Exception" )
220+ whenever(mockKeyStore.deleteEntry(any())).thenThrow(cause)
221+
222+ val exception = assertThrows(DPoPException ::class .java) {
223+ dpopKeyStore.deleteKeyPair()
224+ }
225+ assertEquals(exception.message, DPoPException .KEY_STORE_ERROR .message)
226+ assertThat(exception.cause, `is `(cause))
227+ }
228+ }
0 commit comments