@@ -19,6 +19,8 @@ package com.yubico.authenticator.fido
1919import android.content.Context
2020import android.security.keystore.KeyGenParameterSpec
2121import android.security.keystore.KeyProperties
22+ import org.slf4j.Logger
23+ import org.slf4j.LoggerFactory
2224import java.io.File
2325import java.security.KeyStore
2426import javax.crypto.Cipher
@@ -37,6 +39,8 @@ class PersistentPinUvAuthTokenStore(private val context: Context) {
3739 private const val FILE_NAME = " ppuat.enc"
3840 private const val PAIR_SEPARATOR = " |"
3941 private const val ENTRY_SEPARATOR = " ;"
42+ private val logger: Logger =
43+ LoggerFactory .getLogger(PersistentPinUvAuthTokenStore ::class .java)
4044 }
4145
4246 // Add or update a token for an identifier
@@ -63,29 +67,34 @@ class PersistentPinUvAuthTokenStore(private val context: Context) {
6367 }
6468 }
6569
66- private fun getOrCreateSecretKey (): SecretKey {
67- val keyStore = KeyStore .getInstance(ANDROID_KEY_STORE )
68- keyStore.load(null )
69- val entry = keyStore.getEntry(KEY_ALIAS , null ) as ? KeyStore .SecretKeyEntry
70- return entry?.secretKey ? : run {
71- val keyGenerator =
72- KeyGenerator .getInstance(KeyProperties .KEY_ALGORITHM_AES , ANDROID_KEY_STORE )
73- val keyGenParameterSpec = KeyGenParameterSpec .Builder (
74- KEY_ALIAS ,
75- KeyProperties .PURPOSE_ENCRYPT or KeyProperties .PURPOSE_DECRYPT
76- )
77- .setBlockModes(KeyProperties .BLOCK_MODE_GCM )
78- .setEncryptionPaddings(KeyProperties .ENCRYPTION_PADDING_NONE )
79- .setKeySize(256 )
80- .build()
81- keyGenerator.init (keyGenParameterSpec)
82- keyGenerator.generateKey()
70+ private fun getSecretKey (): SecretKey ? {
71+ val keyStore = KeyStore .getInstance(ANDROID_KEY_STORE ).apply { load(null ) }
72+ return try {
73+ (keyStore.getEntry(KEY_ALIAS , null ) as ? KeyStore .SecretKeyEntry )?.secretKey
74+ } catch (e: Exception ) {
75+ logger.error(" Failed to get secret key: " , e)
76+ null
8377 }
8478 }
8579
80+ private fun createSecretKey (): SecretKey {
81+ val keyGenerator =
82+ KeyGenerator .getInstance(KeyProperties .KEY_ALGORITHM_AES , ANDROID_KEY_STORE )
83+ val keyGenParameterSpec = KeyGenParameterSpec .Builder (
84+ KEY_ALIAS ,
85+ KeyProperties .PURPOSE_ENCRYPT or KeyProperties .PURPOSE_DECRYPT
86+ )
87+ .setBlockModes(KeyProperties .BLOCK_MODE_GCM )
88+ .setEncryptionPaddings(KeyProperties .ENCRYPTION_PADDING_NONE )
89+ .setKeySize(256 )
90+ .build()
91+ keyGenerator.init (keyGenParameterSpec)
92+ return keyGenerator.generateKey()
93+ }
94+
8695 // Serialize the map as "identifier1|ppuat1;identifier2|ppuat2;..."
8796 private fun saveTokens (context : Context , tokens : Map <String , String >) {
88- val secretKey = getOrCreateSecretKey ()
97+ val secretKey = getSecretKey() ? : createSecretKey ()
8998 val cipher = Cipher .getInstance(TRANSFORMATION )
9099 cipher.init (Cipher .ENCRYPT_MODE , secretKey)
91100 val iv = cipher.iv
@@ -105,23 +114,38 @@ class PersistentPinUvAuthTokenStore(private val context: Context) {
105114 private fun loadTokens (context : Context ): Map <String , String > {
106115 val file = File (context.filesDir, FILE_NAME )
107116 if (! file.exists()) return emptyMap()
108- file.inputStream().use { fis ->
109- val ivSize = fis.read()
110- val iv = ByteArray (ivSize)
111- fis.read(iv)
112- val cipher = Cipher .getInstance(TRANSFORMATION )
113- val secretKey = getOrCreateSecretKey()
114- cipher.init (Cipher .DECRYPT_MODE , secretKey, GCMParameterSpec (128 , iv))
115- CipherInputStream (fis, cipher).use { cis ->
116- val data = cis.readBytes().toString(Charsets .UTF_8 )
117- if (data.isBlank()) return emptyMap()
118- return data.split(ENTRY_SEPARATOR )
119- .mapNotNull {
120- val parts = it.split(PAIR_SEPARATOR , limit = 2 )
121- if (parts.size == 2 ) parts[0 ] to parts[1 ] else null
122- }
123- .toMap()
117+
118+ // Try to get the key. If it doesn't exist, we have stale data.
119+ val secretKey = getSecretKey() ? : run {
120+ logger.warn(" PPUAT file exists, but Keystore key is missing. Deleting stale file." )
121+ file.delete()
122+ return emptyMap()
123+ }
124+
125+ return try {
126+ file.inputStream().use { fis ->
127+ val ivSize = fis.read()
128+ if (ivSize == - 1 ) return emptyMap() // Empty file
129+ val iv = ByteArray (ivSize)
130+ fis.read(iv)
131+ val cipher = Cipher .getInstance(TRANSFORMATION )
132+ cipher.init (Cipher .DECRYPT_MODE , secretKey, GCMParameterSpec (128 , iv))
133+ CipherInputStream (fis, cipher).use { cis ->
134+ val data = cis.readBytes().toString(Charsets .UTF_8 )
135+ if (data.isBlank()) return emptyMap()
136+ return data.split(ENTRY_SEPARATOR )
137+ .mapNotNull {
138+ val parts = it.split(PAIR_SEPARATOR , limit = 2 )
139+ if (parts.size == 2 ) parts[0 ] to parts[1 ] else null
140+ }
141+ .toMap()
142+ }
124143 }
144+ } catch (e: Exception ) {
145+ // This catches AEADBadTagException and other potential IOExceptions during decryption.
146+ logger.error(" Failed to decrypt PPUAT file, deleting it. " , e)
147+ file.delete()
148+ emptyMap()
125149 }
126150 }
127151}
0 commit comments