From e851a81f79b11ad72610a713a8e27858224e9143 Mon Sep 17 00:00:00 2001 From: Vitaly Raevsky Date: Tue, 10 Aug 2021 14:19:00 +0300 Subject: [PATCH 01/15] fix build --- app/build.gradle | 1 - app/src/main/AndroidManifest.xml | 10 ++++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 30125db..7dc95ef 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -57,7 +57,6 @@ dependencies { implementation "androidx.navigation:navigation-fragment-ktx:2.3.5" implementation "androidx.navigation:navigation-ui-ktx:2.3.5" implementation "com.google.dagger:hilt-android:2.38.1" - implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03' kapt 'androidx.hilt:hilt-compiler:1.0.0' kapt "com.google.dagger:hilt-android-compiler:2.38.1" } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d74b41a..ac9423e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,6 +13,16 @@ android:name=".App" android:supportsRtl="true" android:theme="@style/Theme.SecureHomeWork"> + + + + + + + + + + \ No newline at end of file From 1f6832d589d7a06f1e868db852e922cf5123a948 Mon Sep 17 00:00:00 2001 From: Vitaly Raevsky Date: Sat, 14 Aug 2021 16:19:28 +0300 Subject: [PATCH 02/15] update api url --- .../main/java/com/otus/securehomework/di/RemoteDataSource.kt | 2 +- .../securehomework/presentation/auth/RegisterFragment.kt | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/otus/securehomework/di/RemoteDataSource.kt b/app/src/main/java/com/otus/securehomework/di/RemoteDataSource.kt index 7d0b2d5..c43808e 100644 --- a/app/src/main/java/com/otus/securehomework/di/RemoteDataSource.kt +++ b/app/src/main/java/com/otus/securehomework/di/RemoteDataSource.kt @@ -10,7 +10,7 @@ import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory -private const val BASE_URL = "http://simplifiedcoding.tech/mywebapp/public/api/" +private const val BASE_URL = "http://auth.tragltech.com/otus/public/api/" class RemoteDataSource { diff --git a/app/src/main/java/com/otus/securehomework/presentation/auth/RegisterFragment.kt b/app/src/main/java/com/otus/securehomework/presentation/auth/RegisterFragment.kt index 0299bf1..e7ef6b7 100644 --- a/app/src/main/java/com/otus/securehomework/presentation/auth/RegisterFragment.kt +++ b/app/src/main/java/com/otus/securehomework/presentation/auth/RegisterFragment.kt @@ -5,14 +5,19 @@ import android.view.View import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import com.otus.securehomework.R +import com.otus.securehomework.databinding.FragmentLoginBinding +import com.otus.securehomework.databinding.FragmentRegisterBinding import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class RegisterFragment : Fragment(R.layout.fragment_register) { + private lateinit var binding: FragmentRegisterBinding private val viewModel by viewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + binding = FragmentRegisterBinding.bind(view) + } } \ No newline at end of file From 1620faf1fb8a6255ec2594289c4050370db19e33 Mon Sep 17 00:00:00 2001 From: Vitaly Raevsky Date: Sun, 15 Aug 2021 16:23:06 +0300 Subject: [PATCH 03/15] fix api url and some issues --- .../data/repository/TokenAuthenticator.kt | 12 ++++-------- .../data/source/local/UserPreferences.kt | 3 +-- .../java/com/otus/securehomework/di/AppModule.kt | 12 ++++++------ .../com/otus/securehomework/di/RemoteDataSource.kt | 12 +++++++----- .../securehomework/presentation/home/HomeActivity.kt | 2 ++ app/src/main/res/layout/activity_auth.xml | 2 +- app/src/main/res/layout/activity_home.xml | 8 ++++---- 7 files changed, 25 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/otus/securehomework/data/repository/TokenAuthenticator.kt b/app/src/main/java/com/otus/securehomework/data/repository/TokenAuthenticator.kt index 3f979cf..1e56e06 100644 --- a/app/src/main/java/com/otus/securehomework/data/repository/TokenAuthenticator.kt +++ b/app/src/main/java/com/otus/securehomework/data/repository/TokenAuthenticator.kt @@ -1,6 +1,5 @@ package com.otus.securehomework.data.repository -import android.content.Context import com.otus.securehomework.data.dto.TokenResponse import com.otus.securehomework.data.source.local.UserPreferences import com.otus.securehomework.data.source.network.TokenRefreshApi @@ -13,19 +12,16 @@ import okhttp3.Route import javax.inject.Inject import com.otus.securehomework.data.Response as DataResponse - class TokenAuthenticator @Inject constructor( - context: Context, - private val tokenApi: TokenRefreshApi + private val tokenApi: TokenRefreshApi, + private val preferences: UserPreferences ) : Authenticator, BaseRepository(tokenApi) { - private val userPreferences = UserPreferences(context) - override fun authenticate(route: Route?, response: Response): Request? { return runBlocking { when (val tokenResponse = getUpdatedToken()) { is DataResponse.Success -> { - userPreferences.saveAccessTokens( + preferences.saveAccessTokens( tokenResponse.value.access_token, tokenResponse.value.refresh_token ) @@ -39,7 +35,7 @@ class TokenAuthenticator @Inject constructor( } private suspend fun getUpdatedToken(): DataResponse { - val refreshToken = userPreferences.refreshToken.first() + val refreshToken = preferences.refreshToken.first() return safeApiCall { tokenApi.refreshAccessToken(refreshToken) } diff --git a/app/src/main/java/com/otus/securehomework/data/source/local/UserPreferences.kt b/app/src/main/java/com/otus/securehomework/data/source/local/UserPreferences.kt index e30dac7..2738111 100644 --- a/app/src/main/java/com/otus/securehomework/data/source/local/UserPreferences.kt +++ b/app/src/main/java/com/otus/securehomework/data/source/local/UserPreferences.kt @@ -14,7 +14,6 @@ class UserPreferences @Inject constructor( private val context: Context ) { - private val Context.dataStore by preferencesDataStore(name = dataStoreFile) val accessToken: Flow get() = context.dataStore.data.map { preferences -> @@ -24,7 +23,6 @@ class UserPreferences val refreshToken: Flow get() = context.dataStore.data.map { preferences -> preferences[REFRESH_TOKEN] - } suspend fun saveAccessTokens(accessToken: String?, refreshToken: String?) { @@ -41,6 +39,7 @@ class UserPreferences } companion object { + private val Context.dataStore by preferencesDataStore(name = dataStoreFile) private val ACCESS_TOKEN = stringPreferencesKey("key_access_token") private val REFRESH_TOKEN = stringPreferencesKey("key_refresh_token") } diff --git a/app/src/main/java/com/otus/securehomework/di/AppModule.kt b/app/src/main/java/com/otus/securehomework/di/AppModule.kt index 955710b..e56a53f 100644 --- a/app/src/main/java/com/otus/securehomework/di/AppModule.kt +++ b/app/src/main/java/com/otus/securehomework/di/AppModule.kt @@ -19,24 +19,24 @@ object AppModule { @Singleton @Provides - fun provideRemoteDataSource(): RemoteDataSource { - return RemoteDataSource() + fun provideRemoteDataSource( + userPreferences: UserPreferences + ): RemoteDataSource { + return RemoteDataSource(userPreferences) } @Provides fun provideAuthApi( remoteDataSource: RemoteDataSource, - @ApplicationContext context: Context ): AuthApi { - return remoteDataSource.buildApi(AuthApi::class.java, context) + return remoteDataSource.buildApi(AuthApi::class.java) } @Provides fun provideUserApi( remoteDataSource: RemoteDataSource, - @ApplicationContext context: Context ): UserApi { - return remoteDataSource.buildApi(UserApi::class.java, context) + return remoteDataSource.buildApi(UserApi::class.java) } @Singleton diff --git a/app/src/main/java/com/otus/securehomework/di/RemoteDataSource.kt b/app/src/main/java/com/otus/securehomework/di/RemoteDataSource.kt index c43808e..b9c66ec 100644 --- a/app/src/main/java/com/otus/securehomework/di/RemoteDataSource.kt +++ b/app/src/main/java/com/otus/securehomework/di/RemoteDataSource.kt @@ -1,24 +1,26 @@ package com.otus.securehomework.di -import android.content.Context import com.otus.securehomework.BuildConfig import com.otus.securehomework.data.repository.TokenAuthenticator +import com.otus.securehomework.data.source.local.UserPreferences import com.otus.securehomework.data.source.network.TokenRefreshApi import okhttp3.Authenticator import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory +import javax.inject.Inject -private const val BASE_URL = "http://auth.tragltech.com/otus/public/api/" +private const val BASE_URL = "https://auth.tragltech.com/otus/api/" -class RemoteDataSource { +class RemoteDataSource @Inject constructor( + private val preferences: UserPreferences +) { fun buildApi( api: Class, - context: Context ): Api { - val authenticator = TokenAuthenticator(context, buildTokenApi()) + val authenticator = TokenAuthenticator(buildTokenApi(), preferences) return Retrofit.Builder() .baseUrl(BASE_URL) .client(getRetrofitClient(authenticator)) diff --git a/app/src/main/java/com/otus/securehomework/presentation/home/HomeActivity.kt b/app/src/main/java/com/otus/securehomework/presentation/home/HomeActivity.kt index 16c01f8..33aea77 100644 --- a/app/src/main/java/com/otus/securehomework/presentation/home/HomeActivity.kt +++ b/app/src/main/java/com/otus/securehomework/presentation/home/HomeActivity.kt @@ -8,9 +8,11 @@ import com.otus.securehomework.R import com.otus.securehomework.data.source.local.UserPreferences import com.otus.securehomework.presentation.auth.AuthActivity import com.otus.securehomework.presentation.startNewActivity +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch import javax.inject.Inject +@AndroidEntryPoint class HomeActivity : AppCompatActivity() { @Inject diff --git a/app/src/main/res/layout/activity_auth.xml b/app/src/main/res/layout/activity_auth.xml index 9f52228..5db0c68 100644 --- a/app/src/main/res/layout/activity_auth.xml +++ b/app/src/main/res/layout/activity_auth.xml @@ -7,7 +7,7 @@ android:layout_height="match_parent" tools:context=".presentation.auth.AuthActivity"> - - Date: Sun, 15 Aug 2021 16:28:59 +0300 Subject: [PATCH 04/15] update readme --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 1b57dd8..a626ad7 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,11 @@ OTUS Репозиторий домашней работы по безопасн ###### _Дополнительно_ 4. Добавьте возможность включать или отключать авторизацию через биометрию +##### _Тестовые пользователь_ + +Email: otus@test.com +Пароль: otus + ##### Материаы, которыми можно пользоваться: [otus_security](https://github.com/vitalyraevsky/otus_security) From 39a77f32d5d421983e6742c414ecfe78515f4f98 Mon Sep 17 00:00:00 2001 From: Vitaly Raevsky Date: Thu, 2 Sep 2021 12:21:03 +0300 Subject: [PATCH 05/15] fix launcher icon --- app/src/main/AndroidManifest.xml | 3 ++- app/src/main/ic_launcher-playstore.png | Bin 0 -> 16343 bytes .../res/drawable/ic_launcher_foreground.xml | 2 +- app/src/main/res/layout/fragment_login.xml | 6 +++--- app/src/main/res/layout/fragment_register.xml | 4 ++-- app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 1841 bytes .../main/res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 3789 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1226 bytes .../main/res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2284 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 2540 bytes .../main/res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 5436 bytes app/src/main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 3907 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 8722 bytes app/src/main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 5446 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 12584 bytes 15 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 app/src/main/ic_launcher-playstore.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ac9423e..792f2f4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -14,7 +14,8 @@ android:supportsRtl="true" android:theme="@style/Theme.SecureHomeWork"> - + diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..725e5b7ad907902189ad446afee745a0f841b486 GIT binary patch literal 16343 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 + android:tint="#3DDC84"> diff --git a/app/src/main/res/layout/fragment_register.xml b/app/src/main/res/layout/fragment_register.xml index c8b2f9e..7f14f4c 100644 --- a/app/src/main/res/layout/fragment_register.xml +++ b/app/src/main/res/layout/fragment_register.xml @@ -9,10 +9,10 @@ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..edd1e39664eb14c8f5e4d2ada62676808368d6d0 GIT binary patch literal 1841 zcmV-12hRA3P);^l=0;w`@Vq#-6e@gP*tXwhm6BoZ%ZVl?WDQ6DsDv1GK4Najf+HFm-E!|!y-R+j{bi2FVTlqZyv5T|c?C#7?+u2LzB)`md_ndFe z|KD?&J?HGAMOLzsm8@i?{7ytvBv?{l6mgw~k_fbzzgH+}&f1g##Ow8ft%BWxU4l>Y z4AdEQhlcgZG-j0I1;ur_pz+$ZYXoLyX7Zj{EsZ{(Z@E&d?nH|IDFWFMFcynN3khfv z1~mzZ@rIy(OcI%w;2H{r9*dj9{|ot!&=m~^gVfd4wNa^ASDMXS#gZf)M6g0M2%1EKK~>Od#GoY}kLOT47{9@M5{gBrBQQuiU8iO68t?IZ z^Nehe!{Imztws&1M6iW2$Q$v~@4jw&d*nMR>HC=O@7XGY*LV+~ArCJ%Ps9zXhE_uc zx!vx=2sWPv#b#pU3QKgtd!AkyI!X`pZo6&_2cIDi@}dmN@_2JVHmIYc<4b7GxYfi(hNJ>{QKnLqMGVB^@ePn024O7e3=C8G=uh-;-%geIIBR;! zX=DGVs`ojFfmofv;kk}e*)?c77AD(d7rj3Gl@Oo0wV-vpG3xH_=FVBB8}_cv&Nq^zD-Cgu3zg3d}JGtzgSYt*Vk>t>{Lt4OP=A zsh!+mea)Z8Fv`24PRUxRg6tXeh?r^K8Tp=?d>3gl8c>x<2N!CToHppd*l*%lC2i$W z!i5?orw#h{YO^YzUbs-BSO#fe8Ki+_kOr1P8dwHtU>T%=Wsn9rW>8&1<#NNIy4)}* zEGqH-mE#GO%LRiz5Ova6c{InM=uAvr;Zs&F7Yy1qdQz?%kFUTSgTQBFzo;r`6dqeI z`2MGivw!SYRUU?U2GvcRQwcQ+kF5uEC9EDO;VO&!BVuK9x|T@Ys65QtNXA zhZ5qn`<<$M4D$@S8kwTvH2Yhbz+>yd5g3ve=i)K&+8fo}a5T>#>?*uC_=QTW5qNAp zeD1L&#K2~w#uN|CGYAXh*bwAmjlg4TAw>i7wvjSMmB%jXq|VDQ&mhnh7*4yJp%)%k zi?dTbw61si_43$&?3o@_y3t#gzey;M*2MFrz<~kC32tN%jo_L!oh% zxO6DX;)E;Kc^>N9p~{B`LU&kF-7f=j!yqsk_UfM5klz!Tq^fbdd?1Q{4&D?q7FH1W zeKI6>3<8s4Ou~4f^?j{;^rUzrk0Y&hF09v4_y}i9dXuJzrXEp0jwY9Y{Xjy4# z=^BT_(c$%aDI5+H0_QX6R+1!9Yip~cq@-jmG?tZ>>F-|3R9swqqqy8DxUZt3VoQB} zeU062Ki<&LP}kVlcua}SW=nX@|JIvlA!5#6>ri)SfEH+iwpFWE-IS<0vsAQl<;t7I z^>)GCf_nt_<~h&+Ew`;$vEmkS&8%xBOTwh3qBSiO*X1JUt@#Wz%xcj;C})u1b|GLT fD_O}(d6E7Fzjkf8Jx5)Z00000NkvXXu0mjf~8L6lYr@e&d1%{JM+)~ zfA^n%X6~I!w{90v7-QWzLVQ5GF4ya_!X55G=!v6z-IwU=bUDI!B*3{4`5J`I_a^is z+(fvA1HW;uw_G05n$Dku@VnhpI(H2rlCXrZmGCton^4}^*jPt!a^QD4zlJg>`x38v zjifW6XuRmc4Q}oR^!qx(vxLtH7IK^c9*-v=h?yY$4!cfAA9$lK8V=0w4U!LogwF4Sc_}A z4$+`≫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 D2}4N8 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..b617b813b21b0def4a5feefb3f7cc408a98b8dbe GIT binary patch literal 1226 zcmV;*1U37KP)Z`3Z=8V?d$uDGm|rCc4n8->FmY6$&<4)=Y5~^ zJZI1B&Y4-Vq!dLQL{!FD<}=Dvjk&KKE$Vie1Sh2mjWrQZ94bTa&_*5X*sDFmW{hK7a>qE<^t2Il<@#L`oACHXIX zbni#n8rn~<2KQ(!j&UCSa*Z2A1NHUwFN<0&@i+!}#nPCnP@~dAwRgU!O~HMda2Ys{ ze&it^buyO>PYtNq3=PeMd6`_McLtAJ#8!stXy@J2^ub8I%?N;e)S(_cGPkD&X0mCz zK0QL8kDoV}39yk-HE@7-kDjBeQ@2=Hntq7>ZWAv8_22;?c;&Gc)IgeN>W|b7`fwy* z6Y~1NK{`C~3*DR^wFt;(Km>Tfk8wOPfD7>b;cqO1zB}|K1!7knFV06X0DxcQ^31?C zp79HbziBG-(Dh<0S_9RCCv0MKp=6+Bpk$zApk%-#OJ*L>uDfTnf!-K6q={Q7yvDa6 z-~~U%@x*}2_bMOreU3b9Zh$zEBNtMcY3)7=`QXPmo)`dd2PunmLm>uw?uX2L@QZy5 zZlHs2wq%iR$Y&tKw;@L-+su6Mdum`(P15$;M{Gi!kk5d^7wp^mUgE9W$0(dmdTT)C z^|^Pv#U|7VZU&g0TH}A2x!?uArv`v)4~A)TDEGEehMNJ*w(+wJKJdypo*2L^?Y?)` zCN>wG3}E+lJbcN_`((7yHv66#0I-+EzE>73BbNc}AF!9G)VHJH0iT>_Xz;`U&=C8R zHU;xwA zSUz}f?Q8&{C#+yRS5;M2SzBAXy{4wd7hvqI9MA$ydBq!n#Omtmr}*Pa#wwq&5?Trt oF9t3aUVBga3C9bz6h#vM0pzO)u3VG3f&c&j07*qoM6N<$f)2S>I{*Lx literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..7f747e66ded6c88e4d274acb32c09ce011e3d65e GIT binary patch literal 2284 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..19a9843e55cc86be93e2b2c1c694c20f5e18a447 GIT binary patch literal 8722 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

HPy zmGS!6Kdhby(I6}-7F3w*fwP`r_q@zT*x zfJoVxIZDhoG9x9WA1P~^aM0r&PU!@K-I0vV5u5sNpu9z~e5w-L3ZSpxm}03(=c}&7 zOGw{S7hm7w#n-`4gjxH0d#*oYACJP~m2#u4kt zPsEA|FR!nVQM*;E)Fh@pu^T1kgVo7qGo19DyXS&|fdS>h7vm21{lpYz#YFO)i$;<;!Mj1FO7+iVc$axI!&j$&mS))DhY7IQ~^as(6U~DB-9&~dD3Y0S- zuh3-p{sdQDH&wA6Orm##y>LLWtfm;@7d2P@l$w zn#F@2eOW!9_tMnakVvR=bi7!*f-zUEf$>{M6eHv2j>Jl)GU9id5%w&x`hdvwPXl0Z zs>wRafRno9_2QVg#{u_7gO#GEwzjr#wJ4ZPOp}!qoI0^W^KL!L7C7}B#vh#+{hDDa z2FPAr!t5D%G0t8Tb=r<)j904+&gXZlX}3XgV)%YZZ+>Nvla^#?}=w)F53H26Wj1iAUg0c2O{yrc(GJ5jw!hz0Z=nI3}Rs%;q}c! z#&Rt|Hpt~M40#NA%SdRh@_GXwr0x##TzaK~pC09ZE!Q+9vp{_Ga7RnO%MGJjmKYH~ zNZH@*8xldzO*TL=Rt;Sgr7G_3f~Q@a!{V;I`5qhXZZ22H3x|xvcEt-QM*x!&Ay0~x z|9VG}Oo=h2tRxqx`0G|g1Q|H8QU_t3DuQ-QTC^nqpG}{wz@#MFx89TKue2(74H~WQ z5ldg&oYormw+kf$)Q|=J*(9|hCNcqNf8@3}Ch3`lD zzfD`5FnpFrT@NU&f$NKWmHZ|?&k$Z&b{;!x(MShaOGNa~kD(1qRi9ab3igmE%UVF6 zFA+ikGUcX8%!pNNybR{z0Reb|{&YlhwndAG8O(}IhkC?#>~74WcG@bJDdZ8kp>bcQ z;FzFeZSuRv?U6csNUqvFcmu{@sdW``PdRm!V#11Mm^BJ{PME=vnI+hvq=^&xzt$@D zbmV`87{R#;EQW$54j~J(?-|60K{O-UL^#RP%9f0>#SWpDDYH!WA^3*d!FTvu{XTB- zEVw_AR}-iYVI8h&`9|xKkxB~yc@++xFDwm2RNJm#95H&(qATXTcyFAQ>o=CzOm4;j t<87vbuc<0{$ufA?9z4pthK-NXT&NmR-R=?Zkas;$l;zdHwX)`6{{uXquxS7Q literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..2d6986ff3bb53c7e16e5fcbff40a1c2ed27abc1f GIT binary patch literal 5446 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..0575a7c0d7ce1b8da00a66b9e19b4439830a611a GIT binary patch literal 12584 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 literal 0 HcmV?d00001 From 8457ffc591307b7906b037c0be4532fb3d12b4c3 Mon Sep 17 00:00:00 2001 From: Vitaly Raevsky Date: Thu, 2 Sep 2021 12:57:35 +0300 Subject: [PATCH 06/15] add network config for ssl --- app/src/main/res/xml/network_security_config.xml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 app/src/main/res/xml/network_security_config.xml diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000..364361d --- /dev/null +++ b/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,9 @@ + + + + auth.tragltech.com + + dvAWD7qJt9UGvNfgA2v2ipbmKOLaGjkHaFI3n/lnao4= + + + \ No newline at end of file From ac4f8e226add3c4096c8957074076d94ac47baf2 Mon Sep 17 00:00:00 2001 From: Vitaly Raevsky Date: Thu, 2 Sep 2021 14:53:59 +0300 Subject: [PATCH 07/15] add pem cert for letsencrypt --- app/src/main/AndroidManifest.xml | 1 + app/src/main/res/raw/lets_encrypt_e1.pem | 17 +++++++++++ app/src/main/res/raw/lets_encrypt_r3.pem | 30 +++++++++++++++++++ .../main/res/xml/network_security_config.xml | 7 +++++ 4 files changed, 55 insertions(+) create mode 100644 app/src/main/res/raw/lets_encrypt_e1.pem create mode 100644 app/src/main/res/raw/lets_encrypt_r3.pem diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 792f2f4..15d41c6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,6 +9,7 @@ android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" + android:networkSecurityConfig="@xml/network_security_config" android:roundIcon="@mipmap/ic_launcher_round" android:name=".App" android:supportsRtl="true" diff --git a/app/src/main/res/raw/lets_encrypt_e1.pem b/app/src/main/res/raw/lets_encrypt_e1.pem new file mode 100644 index 0000000..2a19d41 --- /dev/null +++ b/app/src/main/res/raw/lets_encrypt_e1.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICxjCCAk2gAwIBAgIRALO93/inhFu86QOgQTWzSkUwCgYIKoZIzj0EAwMwTzEL +MAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNo +IEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDIwHhcNMjAwOTA0MDAwMDAwWhcN +MjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3MgRW5j +cnlwdDELMAkGA1UEAxMCRTEwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQkXC2iKv0c +S6Zdl3MnMayyoGli72XoprDwrEuf/xwLcA/TmC9N/A8AmzfwdAVXMpcuBe8qQyWj ++240JxP2T35p0wKZXuskR5LBJJvmsSGPwSSB/GjMH2m6WPUZIvd0xhajggEIMIIB +BDAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB +MBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFFrz7Sv8NsI3eblSMOpUb89V +yy6sMB8GA1UdIwQYMBaAFHxClq7eS0g7+pL4nozPbYupcjeVMDIGCCsGAQUFBwEB +BCYwJDAiBggrBgEFBQcwAoYWaHR0cDovL3gyLmkubGVuY3Iub3JnLzAnBgNVHR8E +IDAeMBygGqAYhhZodHRwOi8veDIuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYG +Z4EMAQIBMA0GCysGAQQBgt8TAQEBMAoGCCqGSM49BAMDA2cAMGQCMHt01VITjWH+ +Dbo/AwCd89eYhNlXLr3pD5xcSAQh8suzYHKOl9YST8pE9kLJ03uGqQIwWrGxtO3q +YJkgsTgDyj2gJrjubi1K9sZmHzOa25JK1fUpE8ZwYii6I4zPPS/Lgul/ +-----END CERTIFICATE----- diff --git a/app/src/main/res/raw/lets_encrypt_r3.pem b/app/src/main/res/raw/lets_encrypt_r3.pem new file mode 100644 index 0000000..43b222a --- /dev/null +++ b/app/src/main/res/raw/lets_encrypt_r3.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw +WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg +RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP +R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx +sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm +NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg +Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG +/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB +Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA +FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw +AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw +Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB +gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W +PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl +ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz +CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm +lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4 +avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2 +yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O +yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids +hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+ +HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv +MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX +nLRbwHOoq7hHwg== +-----END CERTIFICATE----- diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml index 364361d..f0a9cd5 100644 --- a/app/src/main/res/xml/network_security_config.xml +++ b/app/src/main/res/xml/network_security_config.xml @@ -1,5 +1,12 @@ + + + + + + + auth.tragltech.com From 0f7c58a79a7dd1174c6dc10cd73f95edbafabd14 Mon Sep 17 00:00:00 2001 From: Vitaly Raevsky Date: Thu, 2 Sep 2021 21:48:03 +0300 Subject: [PATCH 08/15] change root isrg pem --- app/src/main/res/raw/isrgrootx1.pem | 31 +++++++++++++++++++ app/src/main/res/raw/isrgrootx2.pem | 14 +++++++++ app/src/main/res/raw/lets_encrypt_e1.pem | 17 ---------- app/src/main/res/raw/lets_encrypt_r3.pem | 30 ------------------ .../main/res/xml/network_security_config.xml | 4 +-- 5 files changed, 47 insertions(+), 49 deletions(-) create mode 100644 app/src/main/res/raw/isrgrootx1.pem create mode 100644 app/src/main/res/raw/isrgrootx2.pem delete mode 100644 app/src/main/res/raw/lets_encrypt_e1.pem delete mode 100644 app/src/main/res/raw/lets_encrypt_r3.pem diff --git a/app/src/main/res/raw/isrgrootx1.pem b/app/src/main/res/raw/isrgrootx1.pem new file mode 100644 index 0000000..b85c803 --- /dev/null +++ b/app/src/main/res/raw/isrgrootx1.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- diff --git a/app/src/main/res/raw/isrgrootx2.pem b/app/src/main/res/raw/isrgrootx2.pem new file mode 100644 index 0000000..7d903ed --- /dev/null +++ b/app/src/main/res/raw/isrgrootx2.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw +CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg +R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00 +MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT +ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW ++1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9 +ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI +zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW +tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1 +/q4AaOeMSQ+2b1tbFfLn +-----END CERTIFICATE----- diff --git a/app/src/main/res/raw/lets_encrypt_e1.pem b/app/src/main/res/raw/lets_encrypt_e1.pem deleted file mode 100644 index 2a19d41..0000000 --- a/app/src/main/res/raw/lets_encrypt_e1.pem +++ /dev/null @@ -1,17 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICxjCCAk2gAwIBAgIRALO93/inhFu86QOgQTWzSkUwCgYIKoZIzj0EAwMwTzEL -MAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNo -IEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDIwHhcNMjAwOTA0MDAwMDAwWhcN -MjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3MgRW5j -cnlwdDELMAkGA1UEAxMCRTEwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQkXC2iKv0c -S6Zdl3MnMayyoGli72XoprDwrEuf/xwLcA/TmC9N/A8AmzfwdAVXMpcuBe8qQyWj -+240JxP2T35p0wKZXuskR5LBJJvmsSGPwSSB/GjMH2m6WPUZIvd0xhajggEIMIIB -BDAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB -MBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFFrz7Sv8NsI3eblSMOpUb89V -yy6sMB8GA1UdIwQYMBaAFHxClq7eS0g7+pL4nozPbYupcjeVMDIGCCsGAQUFBwEB -BCYwJDAiBggrBgEFBQcwAoYWaHR0cDovL3gyLmkubGVuY3Iub3JnLzAnBgNVHR8E -IDAeMBygGqAYhhZodHRwOi8veDIuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYG -Z4EMAQIBMA0GCysGAQQBgt8TAQEBMAoGCCqGSM49BAMDA2cAMGQCMHt01VITjWH+ -Dbo/AwCd89eYhNlXLr3pD5xcSAQh8suzYHKOl9YST8pE9kLJ03uGqQIwWrGxtO3q -YJkgsTgDyj2gJrjubi1K9sZmHzOa25JK1fUpE8ZwYii6I4zPPS/Lgul/ ------END CERTIFICATE----- diff --git a/app/src/main/res/raw/lets_encrypt_r3.pem b/app/src/main/res/raw/lets_encrypt_r3.pem deleted file mode 100644 index 43b222a..0000000 --- a/app/src/main/res/raw/lets_encrypt_r3.pem +++ /dev/null @@ -1,30 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw -WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg -RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK -AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP -R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx -sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm -NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg -Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG -/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC -AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB -Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA -FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw -AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw -Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB -gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W -PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl -ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz -CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm -lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4 -avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2 -yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O -yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids -hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+ -HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv -MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX -nLRbwHOoq7hHwg== ------END CERTIFICATE----- diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml index f0a9cd5..224da55 100644 --- a/app/src/main/res/xml/network_security_config.xml +++ b/app/src/main/res/xml/network_security_config.xml @@ -2,8 +2,8 @@ - - + + From 334a0a22281955fb063a5a3821fd7b0eb075c476 Mon Sep 17 00:00:00 2001 From: Vitaly Raevsky Date: Thu, 2 Sep 2021 21:49:44 +0300 Subject: [PATCH 09/15] add trust anchors for domain --- app/src/main/res/xml/network_security_config.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml index 224da55..c2822f8 100644 --- a/app/src/main/res/xml/network_security_config.xml +++ b/app/src/main/res/xml/network_security_config.xml @@ -12,5 +12,9 @@ dvAWD7qJt9UGvNfgA2v2ipbmKOLaGjkHaFI3n/lnao4= + + + + \ No newline at end of file From 94a9f3a4269ed516cd8946666f94d6add6046ce9 Mon Sep 17 00:00:00 2001 From: thechosenone Date: Thu, 9 Sep 2021 21:36:54 +0300 Subject: [PATCH 10/15] secure_auth/added encrypted shared preferences --- app/build.gradle | 1 + .../data/repository/AuthRepository.kt | 6 +-- .../data/repository/TokenAuthenticator.kt | 6 +-- .../securehomework/data/source/crypto/Keys.kt | 40 +++++++++++++++++++ .../source/local/BaseSecurePreferences.kt | 28 +++++++++++++ .../source/local/SecureUserPreferences.kt | 33 +++++++++++++++ .../com/otus/securehomework/di/AppModule.kt | 21 +++++++--- .../securehomework/di/RemoteDataSource.kt | 7 +++- .../presentation/home/HomeActivity.kt | 4 +- .../presentation/splash/SplashActivity.kt | 6 ++- 10 files changed, 136 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/com/otus/securehomework/data/source/crypto/Keys.kt create mode 100644 app/src/main/java/com/otus/securehomework/data/source/local/BaseSecurePreferences.kt create mode 100644 app/src/main/java/com/otus/securehomework/data/source/local/SecureUserPreferences.kt diff --git a/app/build.gradle b/app/build.gradle index 30125db..30fd34b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -58,6 +58,7 @@ dependencies { implementation "androidx.navigation:navigation-ui-ktx:2.3.5" implementation "com.google.dagger:hilt-android:2.38.1" implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03' + implementation "androidx.security:security-crypto:1.1.0-alpha03" kapt 'androidx.hilt:hilt-compiler:1.0.0' kapt "com.google.dagger:hilt-android-compiler:2.38.1" } \ No newline at end of file diff --git a/app/src/main/java/com/otus/securehomework/data/repository/AuthRepository.kt b/app/src/main/java/com/otus/securehomework/data/repository/AuthRepository.kt index 03975c9..c4fca09 100644 --- a/app/src/main/java/com/otus/securehomework/data/repository/AuthRepository.kt +++ b/app/src/main/java/com/otus/securehomework/data/repository/AuthRepository.kt @@ -2,14 +2,14 @@ package com.otus.securehomework.data.repository import com.otus.securehomework.data.Response import com.otus.securehomework.data.dto.LoginResponse -import com.otus.securehomework.data.source.local.UserPreferences +import com.otus.securehomework.data.source.local.SecureUserPreferences import com.otus.securehomework.data.source.network.AuthApi import javax.inject.Inject class AuthRepository @Inject constructor( private val api: AuthApi, - private val preferences: UserPreferences + private val preferences: SecureUserPreferences ) : BaseRepository(api) { suspend fun login( @@ -19,7 +19,7 @@ class AuthRepository return safeApiCall { api.login(email, password) } } - suspend fun saveAccessTokens(accessToken: String, refreshToken: String) { + fun saveAccessTokens(accessToken: String, refreshToken: String) { preferences.saveAccessTokens(accessToken, refreshToken) } } \ No newline at end of file diff --git a/app/src/main/java/com/otus/securehomework/data/repository/TokenAuthenticator.kt b/app/src/main/java/com/otus/securehomework/data/repository/TokenAuthenticator.kt index 3f979cf..b973472 100644 --- a/app/src/main/java/com/otus/securehomework/data/repository/TokenAuthenticator.kt +++ b/app/src/main/java/com/otus/securehomework/data/repository/TokenAuthenticator.kt @@ -2,6 +2,7 @@ package com.otus.securehomework.data.repository import android.content.Context import com.otus.securehomework.data.dto.TokenResponse +import com.otus.securehomework.data.source.local.SecureUserPreferences import com.otus.securehomework.data.source.local.UserPreferences import com.otus.securehomework.data.source.network.TokenRefreshApi import kotlinx.coroutines.flow.first @@ -16,11 +17,10 @@ import com.otus.securehomework.data.Response as DataResponse class TokenAuthenticator @Inject constructor( context: Context, - private val tokenApi: TokenRefreshApi + private val tokenApi: TokenRefreshApi, + private val userPreferences: SecureUserPreferences ) : Authenticator, BaseRepository(tokenApi) { - private val userPreferences = UserPreferences(context) - override fun authenticate(route: Route?, response: Response): Request? { return runBlocking { when (val tokenResponse = getUpdatedToken()) { diff --git a/app/src/main/java/com/otus/securehomework/data/source/crypto/Keys.kt b/app/src/main/java/com/otus/securehomework/data/source/crypto/Keys.kt new file mode 100644 index 0000000..b41c13a --- /dev/null +++ b/app/src/main/java/com/otus/securehomework/data/source/crypto/Keys.kt @@ -0,0 +1,40 @@ +package com.otus.securehomework.data.source.crypto + +import android.content.Context +import android.content.SharedPreferences +import android.os.Build +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import androidx.security.crypto.MasterKey +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject +import javax.inject.Named + +class Keys @Inject constructor( + @ApplicationContext val context: Context +) { + + fun getMasterKey() = MasterKey.Builder(context) + .setSpecs() + .build() + + private fun MasterKey.Builder.setSpecs(): MasterKey.Builder = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + setKeyGenParameterSpec( + KeyGenParameterSpec.Builder( + MasterKey.DEFAULT_MASTER_KEY_ALIAS, + KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT + ) + .setBlockModes(KeyProperties.BLOCK_MODE_GCM) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + .setKeySize(KEY_LENGTH) + .build() + ) + } else { + setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + } + + companion object { + const val KEY_LENGTH = 256 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/otus/securehomework/data/source/local/BaseSecurePreferences.kt b/app/src/main/java/com/otus/securehomework/data/source/local/BaseSecurePreferences.kt new file mode 100644 index 0000000..1191a81 --- /dev/null +++ b/app/src/main/java/com/otus/securehomework/data/source/local/BaseSecurePreferences.kt @@ -0,0 +1,28 @@ +package com.otus.securehomework.data.source.local + +import android.content.Context +import androidx.security.crypto.EncryptedSharedPreferences +import androidx.security.crypto.MasterKey + + +abstract class BaseSecurePreferences(context: Context, masterKey: MasterKey) { + + private val preferences by lazy { + EncryptedSharedPreferences.create( + context, SECURE_PREF_NAME, masterKey, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM + ) + } + + fun put(key: String, value: String) = preferences.edit().putString(key, value).commit() + + fun getString(key: String, defValue: String = ""): String = + preferences.getString(key, defValue) ?: defValue + + fun remove(key: String) = preferences.edit().remove(key).commit() + + companion object { + private const val SECURE_PREF_NAME = "securePreferences" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/otus/securehomework/data/source/local/SecureUserPreferences.kt b/app/src/main/java/com/otus/securehomework/data/source/local/SecureUserPreferences.kt new file mode 100644 index 0000000..e15833e --- /dev/null +++ b/app/src/main/java/com/otus/securehomework/data/source/local/SecureUserPreferences.kt @@ -0,0 +1,33 @@ +package com.otus.securehomework.data.source.local + +import android.content.Context +import androidx.security.crypto.MasterKey +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import javax.inject.Inject + +class SecureUserPreferences @Inject constructor( + context: Context, masterKey: MasterKey +) : BaseSecurePreferences(context, masterKey) { + + val accessToken: Flow + get() = flowOf(getString(ACCESS_TOKEN)) + + val refreshToken: Flow + get() = flowOf(getString(REFRESH_TOKEN)) + + fun saveAccessTokens(accessToken: String?, refreshToken: String?) { + accessToken?.let { put(ACCESS_TOKEN, it) } + refreshToken?.let { put(ACCESS_TOKEN, it) } + } + + fun clear() { + remove(ACCESS_TOKEN) + remove(REFRESH_TOKEN) + } + + companion object { + private const val ACCESS_TOKEN = "secureAccessToken" + private const val REFRESH_TOKEN = "secureRefreshToken" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/otus/securehomework/di/AppModule.kt b/app/src/main/java/com/otus/securehomework/di/AppModule.kt index 955710b..ee2140f 100644 --- a/app/src/main/java/com/otus/securehomework/di/AppModule.kt +++ b/app/src/main/java/com/otus/securehomework/di/AppModule.kt @@ -1,8 +1,11 @@ package com.otus.securehomework.di import android.content.Context +import com.otus.securehomework.App import com.otus.securehomework.data.repository.AuthRepository import com.otus.securehomework.data.repository.UserRepository +import com.otus.securehomework.data.source.crypto.Keys +import com.otus.securehomework.data.source.local.SecureUserPreferences import com.otus.securehomework.data.source.local.UserPreferences import com.otus.securehomework.data.source.network.AuthApi import com.otus.securehomework.data.source.network.UserApi @@ -11,6 +14,7 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent +import javax.inject.Named import javax.inject.Singleton @Module @@ -26,17 +30,19 @@ object AppModule { @Provides fun provideAuthApi( remoteDataSource: RemoteDataSource, - @ApplicationContext context: Context + @ApplicationContext context: Context, + userPreferences: SecureUserPreferences ): AuthApi { - return remoteDataSource.buildApi(AuthApi::class.java, context) + return remoteDataSource.buildApi(AuthApi::class.java, context, userPreferences) } @Provides fun provideUserApi( remoteDataSource: RemoteDataSource, - @ApplicationContext context: Context + @ApplicationContext context: Context, + userPreferences: SecureUserPreferences ): UserApi { - return remoteDataSource.buildApi(UserApi::class.java, context) + return remoteDataSource.buildApi(UserApi::class.java, context, userPreferences) } @Singleton @@ -50,7 +56,7 @@ object AppModule { @Provides fun provideAuthRepository( authApi: AuthApi, - userPreferences: UserPreferences + userPreferences: SecureUserPreferences ): AuthRepository { return AuthRepository(authApi, userPreferences) } @@ -61,4 +67,9 @@ object AppModule { ): UserRepository { return UserRepository(userApi) } + + @Provides + @Singleton + fun provideSecureUserPreferences(@ApplicationContext context: Context, keys: Keys): SecureUserPreferences = + SecureUserPreferences(context, keys.getMasterKey()) } \ No newline at end of file diff --git a/app/src/main/java/com/otus/securehomework/di/RemoteDataSource.kt b/app/src/main/java/com/otus/securehomework/di/RemoteDataSource.kt index 7d0b2d5..e3da99e 100644 --- a/app/src/main/java/com/otus/securehomework/di/RemoteDataSource.kt +++ b/app/src/main/java/com/otus/securehomework/di/RemoteDataSource.kt @@ -3,12 +3,14 @@ package com.otus.securehomework.di import android.content.Context import com.otus.securehomework.BuildConfig import com.otus.securehomework.data.repository.TokenAuthenticator +import com.otus.securehomework.data.source.local.SecureUserPreferences import com.otus.securehomework.data.source.network.TokenRefreshApi import okhttp3.Authenticator import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory +import javax.inject.Inject private const val BASE_URL = "http://simplifiedcoding.tech/mywebapp/public/api/" @@ -16,9 +18,10 @@ class RemoteDataSource { fun buildApi( api: Class, - context: Context + context: Context, + userPreferences: SecureUserPreferences ): Api { - val authenticator = TokenAuthenticator(context, buildTokenApi()) + val authenticator = TokenAuthenticator(context, buildTokenApi(), userPreferences) return Retrofit.Builder() .baseUrl(BASE_URL) .client(getRetrofitClient(authenticator)) diff --git a/app/src/main/java/com/otus/securehomework/presentation/home/HomeActivity.kt b/app/src/main/java/com/otus/securehomework/presentation/home/HomeActivity.kt index 16c01f8..06d2ce0 100644 --- a/app/src/main/java/com/otus/securehomework/presentation/home/HomeActivity.kt +++ b/app/src/main/java/com/otus/securehomework/presentation/home/HomeActivity.kt @@ -5,7 +5,7 @@ import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import com.otus.securehomework.R -import com.otus.securehomework.data.source.local.UserPreferences +import com.otus.securehomework.data.source.local.SecureUserPreferences import com.otus.securehomework.presentation.auth.AuthActivity import com.otus.securehomework.presentation.startNewActivity import kotlinx.coroutines.launch @@ -14,7 +14,7 @@ import javax.inject.Inject class HomeActivity : AppCompatActivity() { @Inject - lateinit var userPreferences: UserPreferences + lateinit var userPreferences: SecureUserPreferences private val viewModel by viewModels() diff --git a/app/src/main/java/com/otus/securehomework/presentation/splash/SplashActivity.kt b/app/src/main/java/com/otus/securehomework/presentation/splash/SplashActivity.kt index 250419e..7cde3af 100644 --- a/app/src/main/java/com/otus/securehomework/presentation/splash/SplashActivity.kt +++ b/app/src/main/java/com/otus/securehomework/presentation/splash/SplashActivity.kt @@ -5,19 +5,23 @@ import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.Observer import androidx.lifecycle.asLiveData import com.otus.securehomework.R +import com.otus.securehomework.data.source.local.SecureUserPreferences import com.otus.securehomework.data.source.local.UserPreferences import com.otus.securehomework.presentation.auth.AuthActivity import com.otus.securehomework.presentation.home.HomeActivity import com.otus.securehomework.presentation.startNewActivity import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject @AndroidEntryPoint class SplashActivity : AppCompatActivity() { + @Inject + lateinit var userPreferences: SecureUserPreferences + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_splash) - val userPreferences = UserPreferences(this) userPreferences.accessToken.asLiveData().observe(this, Observer { val activity = if (it == null) { From defdb15f8107c6d65345b8a5b1f72d5d4bf9375a Mon Sep 17 00:00:00 2001 From: thechosenone Date: Mon, 13 Sep 2021 19:31:33 +0300 Subject: [PATCH 11/15] secure_auth/added biometric auth --- app/build.gradle | 1 + app/src/main/AndroidManifest.xml | 9 ++ .../otus/securehomework/data/dto/LoginData.kt | 7 ++ .../data/repository/AuthRepository.kt | 7 +- .../source/crypto/BiometricAuthManager.kt | 84 ++++++++++++++ .../data/source/crypto/BiometricCipher.kt | 106 ++++++++++++++++++ .../source/local/SecureUserPreferences.kt | 21 ++++ .../com/otus/securehomework/di/AppModule.kt | 7 +- .../presentation/auth/AuthViewModel.kt | 26 ++++- .../presentation/auth/LoginFragment.kt | 4 + .../presentation/home/HomeFragment.kt | 7 ++ .../presentation/home/HomeViewModel.kt | 22 +++- app/src/main/res/layout/fragment_home.xml | 31 +++-- app/src/main/res/values/strings.xml | 2 + 14 files changed, 308 insertions(+), 26 deletions(-) create mode 100644 app/src/main/java/com/otus/securehomework/data/dto/LoginData.kt create mode 100644 app/src/main/java/com/otus/securehomework/data/source/crypto/BiometricAuthManager.kt create mode 100644 app/src/main/java/com/otus/securehomework/data/source/crypto/BiometricCipher.kt diff --git a/app/build.gradle b/app/build.gradle index 30fd34b..064ca2c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -59,6 +59,7 @@ dependencies { implementation "com.google.dagger:hilt-android:2.38.1" implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03' implementation "androidx.security:security-crypto:1.1.0-alpha03" + implementation "androidx.biometric:biometric-ktx:1.2.0-alpha03" kapt 'androidx.hilt:hilt-compiler:1.0.0' kapt "com.google.dagger:hilt-android-compiler:2.38.1" } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d74b41a..f71636b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,6 +13,15 @@ android:name=".App" android:supportsRtl="true" android:theme="@style/Theme.SecureHomeWork"> + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/otus/securehomework/data/dto/LoginData.kt b/app/src/main/java/com/otus/securehomework/data/dto/LoginData.kt new file mode 100644 index 0000000..1deaa29 --- /dev/null +++ b/app/src/main/java/com/otus/securehomework/data/dto/LoginData.kt @@ -0,0 +1,7 @@ +package com.otus.securehomework.data.dto + +data class LoginData(val email: String, val password: String) { + companion object { + val STUB = LoginData("", "") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/otus/securehomework/data/repository/AuthRepository.kt b/app/src/main/java/com/otus/securehomework/data/repository/AuthRepository.kt index c4fca09..08d65b9 100644 --- a/app/src/main/java/com/otus/securehomework/data/repository/AuthRepository.kt +++ b/app/src/main/java/com/otus/securehomework/data/repository/AuthRepository.kt @@ -8,8 +8,7 @@ import javax.inject.Inject class AuthRepository @Inject constructor( - private val api: AuthApi, - private val preferences: SecureUserPreferences + private val api: AuthApi ) : BaseRepository(api) { suspend fun login( @@ -18,8 +17,4 @@ class AuthRepository ): Response { return safeApiCall { api.login(email, password) } } - - fun saveAccessTokens(accessToken: String, refreshToken: String) { - preferences.saveAccessTokens(accessToken, refreshToken) - } } \ No newline at end of file diff --git a/app/src/main/java/com/otus/securehomework/data/source/crypto/BiometricAuthManager.kt b/app/src/main/java/com/otus/securehomework/data/source/crypto/BiometricAuthManager.kt new file mode 100644 index 0000000..e99c09d --- /dev/null +++ b/app/src/main/java/com/otus/securehomework/data/source/crypto/BiometricAuthManager.kt @@ -0,0 +1,84 @@ +package com.otus.securehomework.data.source.crypto + +import android.content.Context +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.biometric.BiometricManager +import androidx.biometric.auth.AuthPromptHost +import androidx.biometric.auth.Class2BiometricAuthPrompt +import androidx.biometric.auth.Class3BiometricAuthPrompt +import androidx.biometric.auth.authenticate +import com.otus.securehomework.data.dto.LoginData +import com.otus.securehomework.data.source.local.SecureUserPreferences +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.flow.first +import javax.inject.Inject + +class BiometricAuthManager @Inject constructor( + @ApplicationContext private val context: Context, + private val biometricCipher: BiometricCipher, + private val userPreferences: SecureUserPreferences +) { + + suspend fun saveBiometricAuth(host: AuthPromptHost) { + userPreferences.saveBiometricData(getBiometricData(host)) + } + + suspend fun removeBiometricAuth(host: AuthPromptHost) { + val savedBiometrics = userPreferences.biometricData.first() + if (savedBiometrics == getBiometricData(host)) userPreferences.removeBiometricData() + } + + suspend fun checkBiometricAuth(host: AuthPromptHost): LoginData { + val savedBiometrics = userPreferences.biometricData.first() + return getBiometricData(host).let { + if (savedBiometrics == it) it.toLoginData() else LoginData.STUB + } + } + + private suspend fun getBiometricData(host: AuthPromptHost) = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)) { + strongBiometricAuth(host) + } else if (canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)) { + weakBiometricAuth(host) + } else "" + + @RequiresApi(Build.VERSION_CODES.M) + private suspend fun strongBiometricAuth(host: AuthPromptHost): String { + val data = userPreferences.tempLoginData.first() + return Class3BiometricAuthPrompt.Builder("Strong biometry", "dismiss").apply { + setSubtitle("Input your biometry") + setDescription("We need your finger") + setConfirmationRequired(true) + } + .build() + .authenticate(host, biometricCipher.getEncryptor()) + .cryptoObject?.cipher?.let { cipher -> + String(biometricCipher.encrypt(data.toValue(), cipher).ciphertext) + } ?: "" + } + + private suspend fun weakBiometricAuth(host: AuthPromptHost): String { + val data = userPreferences.tempLoginData.first() + Class2BiometricAuthPrompt.Builder("Weak biometry", "dismiss").apply { + setSubtitle("Input your biometry") + setDescription("We need your finger") + setConfirmationRequired(true) + } + .build() + .authenticate(host) + return data.toValue() + } + + private fun canAuthenticate(authenticator: Int) = BiometricManager.from(context) + .canAuthenticate(authenticator) == BiometricManager.BIOMETRIC_SUCCESS + + private fun LoginData.toValue() = email + SEPARATOR + password + + private fun String.toLoginData() = split(SEPARATOR).let { LoginData(it[0], it[1]) } + + companion object { + private const val SEPARATOR = "#*#" + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/otus/securehomework/data/source/crypto/BiometricCipher.kt b/app/src/main/java/com/otus/securehomework/data/source/crypto/BiometricCipher.kt new file mode 100644 index 0000000..7dd4141 --- /dev/null +++ b/app/src/main/java/com/otus/securehomework/data/source/crypto/BiometricCipher.kt @@ -0,0 +1,106 @@ +package com.otus.securehomework.data.source.crypto + +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import androidx.annotation.RequiresApi +import androidx.biometric.BiometricPrompt +import dagger.hilt.android.qualifiers.ApplicationContext +import java.security.KeyStore +import javax.crypto.Cipher +import javax.crypto.KeyGenerator +import javax.crypto.SecretKey +import javax.crypto.spec.GCMParameterSpec +import javax.inject.Inject + + +class BiometricCipher @Inject constructor( + @ApplicationContext private val applicationContext: Context +) { + private val keyAlias by lazy { "${applicationContext.packageName}.biometricKey" } + + @RequiresApi(Build.VERSION_CODES.M) + fun getEncryptor(): BiometricPrompt.CryptoObject = BiometricPrompt.CryptoObject( + Cipher.getInstance(TRANSFORMATION) + .apply { init(Cipher.ENCRYPT_MODE, getOrCreateKey()) } + ) + + @RequiresApi(Build.VERSION_CODES.M) + fun getDecryptor(): BiometricPrompt.CryptoObject = BiometricPrompt.CryptoObject( + Cipher.getInstance(TRANSFORMATION) + .apply { + init( + Cipher.DECRYPT_MODE, + getOrCreateKey(), + GCMParameterSpec(AUTH_TAG_SIZE, iv) + ) + } + ) + + fun encrypt(plaintext: String, encryptor: Cipher): EncryptedEntity { + require(plaintext.isNotEmpty()) { "Plaintext cannot be empty" } + val ciphertext = encryptor.doFinal(plaintext.toByteArray()) + return EncryptedEntity( + ciphertext, + encryptor.iv + ) + } + + fun decrypt(ciphertext: ByteArray, decryptor: Cipher): String { + val plaintext = decryptor.doFinal(ciphertext) + return String(plaintext, Charsets.UTF_8) + } + + @RequiresApi(Build.VERSION_CODES.M) + private fun getOrCreateKey(): SecretKey { + val keystore = KeyStore.getInstance(KEYSTORE_PROVIDER).apply { load(null) } + + keystore.getKey(keyAlias, null)?.let { return it as SecretKey } + + val keySpec = KeyGenParameterSpec.Builder( + keyAlias, + KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT + ) + .setBlockModes(KeyProperties.BLOCK_MODE_GCM) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + .setKeySize(KEY_SIZE) + .setUserAuthenticationRequired(true) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + setUnlockedDeviceRequired(true) + + val hasStringBox = applicationContext + .packageManager + .hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE) + + setIsStrongBoxBacked(hasStringBox) + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG) + } + } + .build() + + return KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_PROVIDER) + .apply { init(keySpec) } + .generateKey() + } + + data class EncryptedEntity( + val ciphertext: ByteArray, + val iv: ByteArray + ) + + companion object { + private const val KEYSTORE_PROVIDER = "AndroidKeyStore" + private const val AUTH_TAG_SIZE = 128 + private const val KEY_SIZE = 256 + + private const val TRANSFORMATION = "${KeyProperties.KEY_ALGORITHM_AES}/" + + "${KeyProperties.BLOCK_MODE_GCM}/" + + "${KeyProperties.ENCRYPTION_PADDING_NONE}" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/otus/securehomework/data/source/local/SecureUserPreferences.kt b/app/src/main/java/com/otus/securehomework/data/source/local/SecureUserPreferences.kt index e15833e..0a855d0 100644 --- a/app/src/main/java/com/otus/securehomework/data/source/local/SecureUserPreferences.kt +++ b/app/src/main/java/com/otus/securehomework/data/source/local/SecureUserPreferences.kt @@ -2,6 +2,7 @@ package com.otus.securehomework.data.source.local import android.content.Context import androidx.security.crypto.MasterKey +import com.otus.securehomework.data.dto.LoginData import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf import javax.inject.Inject @@ -16,11 +17,28 @@ class SecureUserPreferences @Inject constructor( val refreshToken: Flow get() = flowOf(getString(REFRESH_TOKEN)) + val biometricData: Flow + get() = flowOf(getString(BIOMETRIC_DATA)) + + val tempLoginData: Flow + get() = flowOf(LoginData(getString(TEMP_EMAIL), getString(TEMP_PASSWORD))) + fun saveAccessTokens(accessToken: String?, refreshToken: String?) { accessToken?.let { put(ACCESS_TOKEN, it) } refreshToken?.let { put(ACCESS_TOKEN, it) } } + fun saveBiometricData(data: String) { + put(BIOMETRIC_DATA, data) + } + + fun saveTempLoginData(data: LoginData) { + put(TEMP_EMAIL, data.email) + put(TEMP_PASSWORD, data.password) + } + + fun removeBiometricData() = remove(BIOMETRIC_DATA) + fun clear() { remove(ACCESS_TOKEN) remove(REFRESH_TOKEN) @@ -29,5 +47,8 @@ class SecureUserPreferences @Inject constructor( companion object { private const val ACCESS_TOKEN = "secureAccessToken" private const val REFRESH_TOKEN = "secureRefreshToken" + private const val BIOMETRIC_DATA = "secureIsBiometricEnabled" + private const val TEMP_EMAIL = "secureTempEmail" + private const val TEMP_PASSWORD = "secureTempPassword" } } \ No newline at end of file diff --git a/app/src/main/java/com/otus/securehomework/di/AppModule.kt b/app/src/main/java/com/otus/securehomework/di/AppModule.kt index ee2140f..47ef989 100644 --- a/app/src/main/java/com/otus/securehomework/di/AppModule.kt +++ b/app/src/main/java/com/otus/securehomework/di/AppModule.kt @@ -1,7 +1,6 @@ package com.otus.securehomework.di import android.content.Context -import com.otus.securehomework.App import com.otus.securehomework.data.repository.AuthRepository import com.otus.securehomework.data.repository.UserRepository import com.otus.securehomework.data.source.crypto.Keys @@ -14,7 +13,6 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent -import javax.inject.Named import javax.inject.Singleton @Module @@ -55,10 +53,9 @@ object AppModule { @Provides fun provideAuthRepository( - authApi: AuthApi, - userPreferences: SecureUserPreferences + authApi: AuthApi ): AuthRepository { - return AuthRepository(authApi, userPreferences) + return AuthRepository(authApi) } @Provides diff --git a/app/src/main/java/com/otus/securehomework/presentation/auth/AuthViewModel.kt b/app/src/main/java/com/otus/securehomework/presentation/auth/AuthViewModel.kt index 505f922..cff3b50 100644 --- a/app/src/main/java/com/otus/securehomework/presentation/auth/AuthViewModel.kt +++ b/app/src/main/java/com/otus/securehomework/presentation/auth/AuthViewModel.kt @@ -1,35 +1,55 @@ package com.otus.securehomework.presentation.auth +import androidx.biometric.auth.AuthPromptHost import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import com.otus.securehomework.data.Response +import com.otus.securehomework.data.dto.LoginData import com.otus.securehomework.data.dto.LoginResponse import com.otus.securehomework.data.repository.AuthRepository +import com.otus.securehomework.data.source.crypto.BiometricAuthManager +import com.otus.securehomework.data.source.local.SecureUserPreferences import com.otus.securehomework.presentation.BaseViewModel import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class AuthViewModel @Inject constructor( - private val repository: AuthRepository + private val repository: AuthRepository, + private val userPreferences: SecureUserPreferences, + private val biometricAuthManager: BiometricAuthManager ) : BaseViewModel(repository) { private val _loginResponse: MutableLiveData> = MutableLiveData() val loginResponse: LiveData> get() = _loginResponse + val hasBiometric: LiveData + get() = userPreferences.biometricData.map { it.isNotEmpty() }.asLiveData() + fun login( email: String, password: String ) = viewModelScope.launch { _loginResponse.value = Response.Loading _loginResponse.value = repository.login(email, password) + userPreferences.saveTempLoginData(LoginData(email, password)) + } + + fun saveAccessTokens(accessToken: String, refreshToken: String) { + userPreferences.saveAccessTokens(accessToken, refreshToken) } - suspend fun saveAccessTokens(accessToken: String, refreshToken: String) { - repository.saveAccessTokens(accessToken, refreshToken) + fun startBiometrics(host: AuthPromptHost) { + viewModelScope.launch { + biometricAuthManager.checkBiometricAuth(host).let { + if (it != LoginData.STUB) login(it.email, it.password) + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/otus/securehomework/presentation/auth/LoginFragment.kt b/app/src/main/java/com/otus/securehomework/presentation/auth/LoginFragment.kt index fe79faf..13fd222 100644 --- a/app/src/main/java/com/otus/securehomework/presentation/auth/LoginFragment.kt +++ b/app/src/main/java/com/otus/securehomework/presentation/auth/LoginFragment.kt @@ -2,6 +2,7 @@ package com.otus.securehomework.presentation.auth import android.os.Bundle import android.view.View +import androidx.biometric.auth.AuthPromptHost import androidx.core.widget.addTextChangedListener import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels @@ -46,6 +47,9 @@ class LoginFragment : Fragment(R.layout.fragment_login) { is Response.Failure -> handleApiError(it) { login() } } }) + viewModel.hasBiometric.observe(viewLifecycleOwner) { + if (it) viewModel.startBiometrics(AuthPromptHost(this)) + } binding.editTextTextPassword.addTextChangedListener { val email = binding.editTextTextEmailAddress.text.toString().trim() binding.buttonLogin.enable(email.isNotEmpty() && it.toString().isNotEmpty()) diff --git a/app/src/main/java/com/otus/securehomework/presentation/home/HomeFragment.kt b/app/src/main/java/com/otus/securehomework/presentation/home/HomeFragment.kt index 201375e..46f3f51 100644 --- a/app/src/main/java/com/otus/securehomework/presentation/home/HomeFragment.kt +++ b/app/src/main/java/com/otus/securehomework/presentation/home/HomeFragment.kt @@ -2,6 +2,7 @@ package com.otus.securehomework.presentation.home import android.os.Bundle import android.view.View +import androidx.biometric.auth.AuthPromptHost import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import com.otus.securehomework.R @@ -40,10 +41,16 @@ class HomeFragment : Fragment(R.layout.fragment_home) { } } }) + viewModel.hasBiometric.observe(viewLifecycleOwner, { + binding.buttonBiometric.text = getString( + if (it) R.string.disable_biometric_auth else R.string.enable_biometric_auth + ) + }) binding.buttonLogout.setOnClickListener { logout() } + binding.buttonBiometric.setOnClickListener { viewModel.switchBiometric(AuthPromptHost(this)) } } private fun updateUI(user: User) { diff --git a/app/src/main/java/com/otus/securehomework/presentation/home/HomeViewModel.kt b/app/src/main/java/com/otus/securehomework/presentation/home/HomeViewModel.kt index 2ea8ebd..b45751d 100644 --- a/app/src/main/java/com/otus/securehomework/presentation/home/HomeViewModel.kt +++ b/app/src/main/java/com/otus/securehomework/presentation/home/HomeViewModel.kt @@ -1,28 +1,48 @@ package com.otus.securehomework.presentation.home +import androidx.biometric.auth.AuthPromptHost import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import com.otus.securehomework.data.Response import com.otus.securehomework.data.dto.LoginResponse import com.otus.securehomework.data.repository.UserRepository +import com.otus.securehomework.data.source.crypto.BiometricAuthManager +import com.otus.securehomework.data.source.local.SecureUserPreferences import com.otus.securehomework.presentation.BaseViewModel import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class HomeViewModel @Inject constructor( - private val repository: UserRepository + private val repository: UserRepository, + private val userPreferences: SecureUserPreferences, + private val biometricAuthManager: BiometricAuthManager ) : BaseViewModel(repository) { private val _user: MutableLiveData> = MutableLiveData() val user: LiveData> get() = _user + private val _hasBiometric: MutableLiveData = MutableLiveData() + val hasBiometric: LiveData + get() = _hasBiometric + fun getUser() = viewModelScope.launch { _user.value = Response.Loading _user.value = repository.getUser() + _hasBiometric.value = userPreferences.biometricData.first().isNotEmpty() + } + + fun switchBiometric(host: AuthPromptHost) = viewModelScope.launch { + if (_hasBiometric.value == true) { + biometricAuthManager.removeBiometricAuth(host) + } else { + biometricAuthManager.saveBiometricAuth(host) + } + _hasBiometric.value = userPreferences.biometricData.first().isNotEmpty() } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index b310964..2d5912a 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -1,6 +1,5 @@ - + style="@style/TextDefault" + android:text="ID" /> + style="@style/TextDefault" + android:text="Name" /> + style="@style/TextDefault" + android:text="Email" />