[automerger skipped] Import translations. DO NOT MERGE ANYWHERE am: 3f45295f7b -s ours
am skip reason: subject contains skip directive
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/KeyChain/+/15591122
Change-Id: Ie94421a512998ccf655e88d9101ad30ccd8621e5
diff --git a/Android.bp b/Android.bp
index 456ddd6..6f8bd07 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12,8 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
android_app {
name: "KeyChain",
+ static_libs: [
+ "bouncycastle-unbundled",
+ "com.google.android.material_material",
+ ],
srcs: ["src/**/*.java"],
platform_apis: true,
certificate: "platform",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 9d1601f..fae32b6 100755
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -11,13 +11,16 @@
android:allowBackup="false"
android:usesCleartextTraffic="false"
android:theme="@android:style/Theme.DeviceDefault.DayNight">
- <service android:name="com.android.keychain.KeyChainService">
+ <service android:name="com.android.keychain.KeyChainService"
+ android:exported="true">
<intent-filter>
<action android:name="android.security.IKeyChainService"/>
</intent-filter>
</service>
<activity android:name="com.android.keychain.KeyChainActivity"
+ android:exported="true"
android:theme="@style/KeyChainTransparent"
+ android:launchMode="singleTop"
android:excludeFromRecents="true">
<intent-filter>
<action android:name="com.android.keychain.CHOOSER"/>
diff --git a/OWNERS b/OWNERS
index 907b626..7218804 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,5 +1,5 @@
# Core Enterprise security team
-eranm@google.com
+acjohnston@google.com
pgrafov@google.com
rubinxu@google.com
@@ -12,3 +12,4 @@
# Emeritus owners
rgl@google.com
+eranm@google.com
diff --git a/res/layout/keychain_activity.xml b/res/layout/keychain_activity.xml
new file mode 100644
index 0000000..b96c504
--- /dev/null
+++ b/res/layout/keychain_activity.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <androidx.coordinatorlayout.widget.CoordinatorLayout
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_alignParentBottom="true"
+ android:layout_centerHorizontal="true" />
+
+</RelativeLayout>
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index dec5f2d..fe50c19 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Jy kan sertifikate installeer van \'n PKCS#12-lêer met \'n %1$s- of \'n %2$s-uitbreiding wat in eksterne berging geleë is."</string>
<string name="allow_button" msgid="3030990695030371561">"Kies"</string>
<string name="deny_button" msgid="3766539809121892584">"Weier"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Laai tans sertifikate …"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s soek tans vir \'n sertifikaat …"</string>
</resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index e2b44f0..819529c 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"የ%1$s ወይም %2$s ቅጥያቸው ውጫዊ ማከማቻ ላይ የሚገኝ የPKCS#12 ፋይል የእውቅና ማረጋገጫ ምስክር ወረቀቶችን መጫን ይችላሉ።"</string>
<string name="allow_button" msgid="3030990695030371561">"ምረጥ"</string>
<string name="deny_button" msgid="3766539809121892584">"ከልክል"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"የእውቅና ማረጋገጫ በመጫን ላይ..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s የእውቅና ማረጋገጫን በመፈተሽ ላይ ነው…"</string>
</resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index fff837f..f98a0ac 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"يمكنك تثبيت شهادات من ملف PKCS#12 بامتداد %1$s أو %2$s، حيث إن هذا الملف موجود في وحدة التخزين الخارجية."</string>
<string name="allow_button" msgid="3030990695030371561">"تحديد"</string>
<string name="deny_button" msgid="3766539809121892584">"رفض"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"جارٍ تحميل الشهادات..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"جارٍ التحقّق من قِبل %s بحثًا عن شهادة…"</string>
</resources>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index b8f054d..b98f03b 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"আপুনি বাহ্যিক সঞ্চয়াগাৰত থকা %1$s বা %2$s এক্সটেনশ্বনযুক্ত এটা PKCS#12 ফাইলৰ পৰা প্ৰমাণপত্ৰসমূহ ইনষ্টল কৰিব পাৰে৷"</string>
<string name="allow_button" msgid="3030990695030371561">"বাছনি কৰক"</string>
<string name="deny_button" msgid="3766539809121892584">"প্ৰত্যাখ্যান কৰক"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"প্ৰমাণপত্ৰসমূহ ল’ড কৰি থকা হৈছে..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%sএ প্ৰমাণপত্ৰ আছে নে নাই সেয়া পৰীক্ষা কৰি আছে…"</string>
</resources>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 9d97183..a44203a 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Siz sertifikatları xarici yaddaşda yerləşən, %1$s və ya %2$s artırması olan PKCS#12 fayldan quraşdıra bilərsiniz."</string>
<string name="allow_button" msgid="3030990695030371561">"Seçin"</string>
<string name="deny_button" msgid="3766539809121892584">"İmtina et"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Sertifikatlar yüklənir..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s sertifikatı yoxlayır…"</string>
</resources>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index da2c756..a607e37 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Možete da instalirate sertifikate iz PKCS#12 datoteke sa ekstenzijom %1$s ili %2$s koji se nalazi u spoljnoj memoriji."</string>
<string name="allow_button" msgid="3030990695030371561">"Izaberi"</string>
<string name="deny_button" msgid="3766539809121892584">"Odbij"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Učitavaju se sertifikati…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s proverava da li postoji sertifikat…"</string>
</resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index f99b572..de554f3 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Сертыфікаты можна ўсталяваць з файла PKCS#12 з пашырэннем %1$s або %2$s, якi знаходзіцца на вонкавым сховішчы."</string>
<string name="allow_button" msgid="3030990695030371561">"Выбраць"</string>
<string name="deny_button" msgid="3766539809121892584">"Адмовіць"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Ідзе загрузка сертыфікатаў…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s правярае наяўнасць сертыфіката…"</string>
</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 3cd43ac..df3c993 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Можете да инсталирате сертификати от файл с формат PKCS#12 с разширение %1$s или %2$s, намиращ се във външното хранилище."</string>
<string name="allow_button" msgid="3030990695030371561">"Избиране"</string>
<string name="deny_button" msgid="3766539809121892584">"Отказ"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Сертификатите се зареждат..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s проверява за сертификат…"</string>
</resources>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index c63fe73..d9b8e05 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"বহিরাগত সঞ্চয়স্থানে অবস্থিত %1$s বা %2$s এক্সটেনশান সহ PKCS#12 ফাইল থেকে আপনি আপনার সার্টিফিকেটগুলি ইনস্টল করতে পারেন।"</string>
<string name="allow_button" msgid="3030990695030371561">"বেছে নিন"</string>
<string name="deny_button" msgid="3766539809121892584">"অস্বীকার করুন"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"সার্টিফিকেট লোড করা হচ্ছে..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s সার্টিফিকেট আছে কিনা চেক করছে…"</string>
</resources>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 94f708c..e526907 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Možete instalirati certifikate iz PKCS#12 fajla sa ekstenzijom %1$s ili %2$s, koji se nalazi u vanjskoj pohrani."</string>
<string name="allow_button" msgid="3030990695030371561">"Odaberi"</string>
<string name="deny_button" msgid="3766539809121892584">"Odbij"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Učitavanje potvrda..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s provjerava postoji li certifikat…"</string>
</resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index d4156b0..5d3eb57 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Pots instal·lar certificats des d\'un fitxer PKCS#12 amb una extensió %1$s o %2$s ubicada en un emmagatzematge extern."</string>
<string name="allow_button" msgid="3030990695030371561">"Selecciona"</string>
<string name="deny_button" msgid="3766539809121892584">"Denega"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"S\'estan carregant els certificats..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s està cercant un certificat…"</string>
</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 35e9982..b4c0645 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Certifikáty můžete instalovat ze souboru PKCS#12 s příponou %1$s nebo %2$s, umístěného v externím úložišti."</string>
<string name="allow_button" msgid="3030990695030371561">"Vybrat"</string>
<string name="deny_button" msgid="3766539809121892584">"Odmítnout"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Načítání certifikátů..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s vyhledává certifikát…"</string>
</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 7ebec70..1828cfa 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Du kan installere certifikater fra en PKCS#12-fil med en %1$s- eller en %2$s-udvidelse, der er placeret på et eksternt lager."</string>
<string name="allow_button" msgid="3030990695030371561">"Vælg"</string>
<string name="deny_button" msgid="3766539809121892584">"Afvis"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Indlæser certifikater..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s indlæser et certifikat…"</string>
</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index c7cf5d4..135aa80 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Du kannst Zertifikate aus einer PKCS#12-Datei mit einer %1$s- oder %2$s-Erweiterung installieren, die sich auf externen Speichermedien befinden."</string>
<string name="allow_button" msgid="3030990695030371561">"Auswählen"</string>
<string name="deny_button" msgid="3766539809121892584">"Ablehnen"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Zertifikate werden geladen…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s sucht nach einem Zertifikat…"</string>
</resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 034df08..7d183e5 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Μπορείτε να εγκαταστήσετε πιστοποιητικά από ένα αρχείο PKCS#12 με επέκταση %1$s ή %2$s που βρίσκονται σε εξωτερική συσκευή αποθήκευσης."</string>
<string name="allow_button" msgid="3030990695030371561">"Επιλογή"</string>
<string name="deny_button" msgid="3766539809121892584">"Απόρριψη"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Φόρτωση πιστοποιητικών…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"Το %s ελέγχει για πιστοποιητικό…"</string>
</resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 5962360..4df0c2b 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"You can install certificates from a PKCS#12 file with a %1$s or a %2$s extension located in external storage."</string>
<string name="allow_button" msgid="3030990695030371561">"Select"</string>
<string name="deny_button" msgid="3766539809121892584">"Deny"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Loading certificates..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s is checking for a certificate…"</string>
</resources>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index 5962360..4df0c2b 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"You can install certificates from a PKCS#12 file with a %1$s or a %2$s extension located in external storage."</string>
<string name="allow_button" msgid="3030990695030371561">"Select"</string>
<string name="deny_button" msgid="3766539809121892584">"Deny"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Loading certificates..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s is checking for a certificate…"</string>
</resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 5962360..4df0c2b 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"You can install certificates from a PKCS#12 file with a %1$s or a %2$s extension located in external storage."</string>
<string name="allow_button" msgid="3030990695030371561">"Select"</string>
<string name="deny_button" msgid="3766539809121892584">"Deny"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Loading certificates..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s is checking for a certificate…"</string>
</resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 5962360..4df0c2b 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"You can install certificates from a PKCS#12 file with a %1$s or a %2$s extension located in external storage."</string>
<string name="allow_button" msgid="3030990695030371561">"Select"</string>
<string name="deny_button" msgid="3766539809121892584">"Deny"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Loading certificates..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s is checking for a certificate…"</string>
</resources>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index cc9fbc9..f0c015c 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"You can install certificates from a PKCS#12 file with a %1$s or a %2$s extension located in external storage."</string>
<string name="allow_button" msgid="3030990695030371561">"Select"</string>
<string name="deny_button" msgid="3766539809121892584">"Deny"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Loading certificates..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s is checking for a certificate…"</string>
</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 051841d..97b0af2 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Puedes instalar los certificados desde un archivo PKCS#12 con extensión %1$s o %2$s a través de una ubicación de almacenamiento externa."</string>
<string name="allow_button" msgid="3030990695030371561">"Seleccionar"</string>
<string name="deny_button" msgid="3766539809121892584">"Rechazar"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Cargando certificados…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s está buscando un certificado…"</string>
</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 0d5574f..7bef59f 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Puedes instalar certificados desde un archivo PKCS#12 con extensión %1$s o %2$s ubicado en el almacenamiento externo."</string>
<string name="allow_button" msgid="3030990695030371561">"Seleccionar"</string>
<string name="deny_button" msgid="3766539809121892584">"Denegar"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Cargando certificados..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s está comprobando si hay un certificado…"</string>
</resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 0757465..0bbc664 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Võite installida sertifikaadid failist PKCS#12 laiendiga %1$s või %2$s, mis asub välismäluseadmel."</string>
<string name="allow_button" msgid="3030990695030371561">"Vali"</string>
<string name="deny_button" msgid="3766539809121892584">"Keeldu"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Sertifikaatide laadimine …"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s kontrollib, kas sertifikaat on olemas …"</string>
</resources>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index c7fc130..60a48fb 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Ziurtagiriak kanpoko memorian daukazun eta %1$s edo %2$s luzapena duen PKCS#12 fitxategi batetik instala ditzakezu."</string>
<string name="allow_button" msgid="3030990695030371561">"Hautatu"</string>
<string name="deny_button" msgid="3766539809121892584">"Ukatu"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Ziurtagiriak kargatzen…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s ziurtagiri bat egiaztatzen ari da…"</string>
</resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 1f1fddf..563be4f 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"با یک افرونه %2$s یا %1$s واقع در حافظه خارجی میتوانید مجوزهارا از یک فایل PKCS#12 نصب کنید."</string>
<string name="allow_button" msgid="3030990695030371561">"انتخاب"</string>
<string name="deny_button" msgid="3766539809121892584">"مجاز نبودن"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"درحال بار کردن مجوز..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s درحال بررسی گواهینامه است…"</string>
</resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 8542c43..98b6812 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Voit asentaa varmenteita PKCS#12-tiedostosta, jos ulkoisessa tallennustilassa on %1$s tai %2$s-laajennus."</string>
<string name="allow_button" msgid="3030990695030371561">"Valitse"</string>
<string name="deny_button" msgid="3766539809121892584">"Hylkää"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Ladataan varmenteita…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s tarkistaa varmennetta…"</string>
</resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 60b86bc..7dbd53e 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Vous pouvez installer des certificats à partir d\'un fichier PKCS#12 doté d\'une extension %1$s ou %2$s conservée dans la mémoire de stockage externe."</string>
<string name="allow_button" msgid="3030990695030371561">"Sélectionner"</string>
<string name="deny_button" msgid="3766539809121892584">"Refuser"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Chargement des certificats en cours…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s recherche un certificat…"</string>
</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 44ec4d0..0dadaf9 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Vous pouvez installer des certificats à partir d\'un fichier PKCS#12 doté d\'une extension %1$s ou %2$s conservée dans la mémoire de stockage externe."</string>
<string name="allow_button" msgid="3030990695030371561">"Choisir"</string>
<string name="deny_button" msgid="3766539809121892584">"Refuser"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Chargement des certificats…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s recherche un certificat…"</string>
</resources>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index ebb2610..f8823be 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Podes instalar certificados dun ficheiro PKCS#12 cunha extensión %1$s ou %2$s situado no almacenamento externo."</string>
<string name="allow_button" msgid="3030990695030371561">"Seleccionar"</string>
<string name="deny_button" msgid="3766539809121892584">"Rexeitar"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Cargando certificados…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s está cargando un certificado…"</string>
</resources>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 7b8ff8e..777b944 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"તમે બાહ્ય સ્ટોરેજ પર સ્થિત %1$s અથવા %2$s એક્સ્ટેન્શન સાથે PKCS#12 ફાઇલમાંથી પ્રમાણપત્રો ઇન્સ્ટૉલ કરી શકો છો."</string>
<string name="allow_button" msgid="3030990695030371561">"પસંદ કરો"</string>
<string name="deny_button" msgid="3766539809121892584">"નકારો"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"પ્રમાણપત્રો લોડ કરી રહ્યાં છીએ..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s પ્રમાણપત્ર ચેક કરી રહ્યાં છીએ…"</string>
</resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index e9a72d4..1313191 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"आप बाहरी मेमोरी में मौजूद %1$s या %2$s एक्सटेंशन वाली PKCS#12 फ़ाइल से प्रमाणपत्र इंस्टॉल कर सकते हैं."</string>
<string name="allow_button" msgid="3030990695030371561">"चुनें"</string>
<string name="deny_button" msgid="3766539809121892584">"नामंज़ूर करें"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"प्रमाणपत्र लोड हो रहे हैं..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s प्रमाणपत्र की जांच की जा रही है…"</string>
</resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index f9827b0..42bd92c 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Možete instalirati certifikate iz datoteke PKCS#12 s proširenjem %1$s ili %2$s locirane u vanjskom spremištu."</string>
<string name="allow_button" msgid="3030990695030371561">"Odaberi"</string>
<string name="deny_button" msgid="3766539809121892584">"Odbij"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Učitavanje certifikata..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s provjerava certifikat…"</string>
</resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 3c34808..aec30e8 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"PKCS#12 fájlból külső tárolóban található %1$s vagy %2$s bővítmény segítségével telepíthet tanúsítványokat."</string>
<string name="allow_button" msgid="3030990695030371561">"Választás"</string>
<string name="deny_button" msgid="3766539809121892584">"Elutasítás"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Tanúsítványok betöltése…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"A(z) %s tanúsítványt keres…"</string>
</resources>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index a96c9de..177836f 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Կարող եք հավաստագրեր տեղադրել %1$s կամ %2$s ընդլայնումով PKCS#12 ֆայլից, որը գտնվում է արտաքին կրիչի վրա։"</string>
<string name="allow_button" msgid="3030990695030371561">"Ընտրել"</string>
<string name="deny_button" msgid="3766539809121892584">"Մերժել"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Հավաստագրերի բեռնում..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s հավելվածը ստուգում է հավաստագրի առկայությունը…"</string>
</resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index c53ae0f..f399f2a 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Anda dapat menginstal sertifikat dari file PKCS#12 dengan ekstensi %1$s atau %2$s yang terletak di penyimpanan eksternal."</string>
<string name="allow_button" msgid="3030990695030371561">"Pilih"</string>
<string name="deny_button" msgid="3766539809121892584">"Tolak"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Memuat sertifikat..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s sedang memeriksa sertifikat…"</string>
</resources>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 7188bbc..c67c47f 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Þú getur sett upp vottorð úr PKCS#12-skrá með endinguna %1$s eða %2$s sem finna má í ytri geymslunni."</string>
<string name="allow_button" msgid="3030990695030371561">"Velja"</string>
<string name="deny_button" msgid="3766539809121892584">"Hafna"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Hleður vottorð..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s leitar að vottorði…"</string>
</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index ebc6971..cd8599b 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Puoi installare certificati da un file PKCS#12 con un\'estensione %1$s o %2$s che si trova nella memoria esterna."</string>
<string name="allow_button" msgid="3030990695030371561">"Seleziona"</string>
<string name="deny_button" msgid="3766539809121892584">"Rifiuta"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Caricamento dei certificati…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s sta controllando se è presente un certificato…"</string>
</resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index ea04f37..ec38208 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"ניתן להתקין אישורים מקובץ PKCS#12 עם סיומת %1$s או %2$s הממוקם באחסון חיצוני."</string>
<string name="allow_button" msgid="3030990695030371561">"בחירה"</string>
<string name="deny_button" msgid="3766539809121892584">"דחייה"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"טעינת האישורים מתבצעת…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"מתבצע חיפוש אישורים על ידי %s…"</string>
</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 2106384..c880836 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"外部ストレージにある、拡張子が%1$sまたは%2$sのPKCS#12ファイルから証明書をインストールできます。"</string>
<string name="allow_button" msgid="3030990695030371561">"選択"</string>
<string name="deny_button" msgid="3766539809121892584">"拒否"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"証明書を読み込んでいます..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s が証明書を確認しています…"</string>
</resources>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 88d7352..aa0e7e4 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"თქვენ შეგიძლიათ დააყენოთ სერტიფიკატი %1$s ან %2$s გაფართოების ფაილიდან - PKCS#12, რომელიც გარე მეხსიერებაში მდებარეობს."</string>
<string name="allow_button" msgid="3030990695030371561">"არჩევა"</string>
<string name="deny_button" msgid="3766539809121892584">"უარყოფა"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"მიმდინარეობს სერტიფიკატების ჩატვირთვა..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s ამოწმებს სერტიფიკატის არსებობას…"</string>
</resources>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 01aa8e4..180c912 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Сертификаттарды сыртқы жадта орналасқан %1$s немесе %2$s жалғаулы PKCS#12 файлынан орнатуға болады."</string>
<string name="allow_button" msgid="3030990695030371561">"Таңдау"</string>
<string name="deny_button" msgid="3766539809121892584">"Тыйым салу"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Сертификаттар жүктеп алынуда..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s сертификатты тексеруде…"</string>
</resources>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 1e842d6..b80652d 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"អ្នកអាចដំឡើងវិញ្ញាបនបត្រពីឯកសារ PKCS#12 ជាមួយផ្នែកបន្ថែម %1$s ឬ %2$s ដែលស្ថិតក្នុងឧបករណ៍ផ្ទុកខាងក្រៅ។"</string>
<string name="allow_button" msgid="3030990695030371561">"ជ្រើស"</string>
<string name="deny_button" msgid="3766539809121892584">"បដិសេធ"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"កំពុងផ្ទុកវិញ្ញាបនបត្រ..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s កំពុងពិនិត្យមើលវិញ្ញាបនបត្រ…"</string>
</resources>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index b10decf..8d6b506 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"ಬಾಹ್ಯ ಸಂಗ್ರಹಣೆಯಲ್ಲಿ ಸಂಗ್ರಹವಾಗಿರುವ %1$s ಅಥವಾ %2$s ವಿಸ್ತರಣೆಯೊಂದಿಗೆ PKCS#12 ಫೈಲ್ನಿಂದ ನೀವು ಪ್ರಮಾಣಪತ್ರಗಳನ್ನು ಇನ್ಸ್ಟಾಲ್ ಮಾಡಬಹುದು."</string>
<string name="allow_button" msgid="3030990695030371561">"ಆಯ್ಕೆಮಾಡಿ"</string>
<string name="deny_button" msgid="3766539809121892584">"ನಿರಾಕರಿಸಿ"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"ಪ್ರಮಾಣಪತ್ರವನ್ನು ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s ಪ್ರಮಾಣಪತ್ರ ಪರಿಶೀಲಿಸುತ್ತಿದೆ…"</string>
</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 0c49e92..01f1e55 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"PKCS#12 파일에서 외부 저장소에 있는 확장 프로그램 %1$s 또는 %2$s을(를) 사용하여 인증서를 설치할 수 있습니다."</string>
<string name="allow_button" msgid="3030990695030371561">"선택"</string>
<string name="deny_button" msgid="3766539809121892584">"거부"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"인증서 로드 중..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s에서 인증서 확인 중…"</string>
</resources>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 058c450..3328e4c 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"PKCS#12 файлындагы тастыктамаларды тышкы сактагычта жайгашкан %1$s же %2$s кеңейтмеси менен орнотсоңуз болот."</string>
<string name="allow_button" msgid="3030990695030371561">"Тандоо"</string>
<string name="deny_button" msgid="3766539809121892584">"Баш тартуу"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Тастыктамалар жүктөлүүдө..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s тастыктаманы текшерүүдө…"</string>
</resources>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index bec8b2f..d3ec800 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"ທ່ານສາມາດຕິດຕັ້ງໃບຮັບຮອງຈາກໄຟລ໌ PKCS#12 ທີ່ມີນາມສະກຸນ %1$s ຫຼື %2$s ຢູ່ໃນພື້ນທີ່ຈັດເກັບຂໍ້ມູນພາຍນອກໄດ້."</string>
<string name="allow_button" msgid="3030990695030371561">"ເລືອກ"</string>
<string name="deny_button" msgid="3766539809121892584">"ປະຕິເສດ"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"ກຳລັງໂຫຼດໃບຮັບຮອງ..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s ກຳລັງກວດສອບໃບຮັບຮອງຢູ່…"</string>
</resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index d21776f..697c777 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Galite įdiegti sertifikatus iš failo PKCS#12 naudodami išorinėje saugykloje esantį plėtinį %1$s arba %2$s."</string>
<string name="allow_button" msgid="3030990695030371561">"Pasirinkti"</string>
<string name="deny_button" msgid="3766539809121892584">"Atmesti"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Įkeliami sertifikatai..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"Tikrinama, ar yra „%s“ sertifikatas…"</string>
</resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 8734df8..676ae33 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Jūs varat instalēt sertifikātus no PKCS#12 faila ar %1$s vai %2$s paplašinājumu, kas atrodas ārējā atmiņā."</string>
<string name="allow_button" msgid="3030990695030371561">"Atlasīt"</string>
<string name="deny_button" msgid="3766539809121892584">"Noraidīt"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Notiek sertifikātu ielāde..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s pārbauda, vai ir sertifikāts…"</string>
</resources>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index e3599be..88608ad 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Може да инсталирате сертификати од датотеката PKCS#12 со наставка %1$s или %2$s што се наоѓа на надворешната меморија."</string>
<string name="allow_button" msgid="3030990695030371561">"Избери"</string>
<string name="deny_button" msgid="3766539809121892584">"Одбиј"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Се вчитуваат сертификати…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s врши проверка за сертификат…"</string>
</resources>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 3d1ce38..6488515 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"ബാഹ്യസംഭരണത്തിലുള്ള %1$s അല്ലെങ്കിൽ %2$s എക്സ്റ്റൻഷൻ ഉപയോഗിച്ച് നിങ്ങൾക്ക് ഒരു PKCS#12 ഫയലിൽ സർട്ടിഫിക്കറ്റുകൾ ഇൻസ്റ്റാളുചെയ്യാനാകും."</string>
<string name="allow_button" msgid="3030990695030371561">"തിരഞ്ഞെടുക്കുക"</string>
<string name="deny_button" msgid="3766539809121892584">"നിരസിക്കുക"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"സർട്ടിഫിക്കറ്റുകൾ ലോഡ് ചെയ്യുന്നു..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s സർട്ടിഫിക്കറ്റ് പരിശോധിക്കുകയാണ്…"</string>
</resources>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index f250ab0..98315e9 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Та %1$s эсхүл %2$s өргөтгэлтэй гадаад хадгалах санд байрласан PKCS#12 файлаас сертификатуудыг суулгаж болно."</string>
<string name="allow_button" msgid="3030990695030371561">"Сонгох"</string>
<string name="deny_button" msgid="3766539809121892584">"Татгалзах"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Сертификатыг ачаалж байна..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s сертификатыг шалгаж байна…"</string>
</resources>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 61413cc..e036d40 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"तुम्ही अतिरिक्त स्टोरेजमध्ये स्थित %1$s किंवा %2$s विस्तारासह एका PKCS#12 फाइलवरुन प्रमाणपत्र इंस्टॉल करु शकता."</string>
<string name="allow_button" msgid="3030990695030371561">"निवडा"</string>
<string name="deny_button" msgid="3766539809121892584">"नकार द्या"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"प्रमाणपत्र लोड करत आहे…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s प्रमाणपत्र तपासात आहे…"</string>
</resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 0619ee8..bf7b1c6 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Anda boleh memasang sijil daripada fail PKCS#12 dengan sambungan %1$s atau %2$s yang terletak dalam storan luaran."</string>
<string name="allow_button" msgid="3030990695030371561">"Pilih"</string>
<string name="deny_button" msgid="3766539809121892584">"Nafikan"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Memuatkan sijil..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s sedang menyemak sijil…"</string>
</resources>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index a7eef5a..4e9eab6 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"အသိအမှတ်ပြုလက်မှတ်များကို ပြင်ပ သိုလှောင်ရာများမှာရှိသော %1$s သို့မဟုတ် %2$s ဖိုင်နောက်ဆက်တွဲပါသော PKCS#12 ဖိုင် များမှ ထည့်သွင်းနိုင်သည်။"</string>
<string name="allow_button" msgid="3030990695030371561">"ရွေးရန်"</string>
<string name="deny_button" msgid="3766539809121892584">"ငြင်းပယ်ရန်"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"အသိအမှတ်ပြုလက်မှတ်များကို ဖွင့်နေသည်..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s က အသိအမှတ်ပြုလက်မှတ်ကို ရှာဖွေနေသည်…"</string>
</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index a7ba7a9..d48538c 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Du kan installere sertifikater fra en PKCS#12-fil med %1$s eller %2$s-utvidelse lagret på ekstern lagring."</string>
<string name="allow_button" msgid="3030990695030371561">"Velg"</string>
<string name="deny_button" msgid="3766539809121892584">"Avslå"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Laster inn sertifikater …"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s ser etter et sertifikat …"</string>
</resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 505528c..5608497 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"तपाइँले प्रमाणपत्रहरू बाह्य भण्डारणमा अवस्थित %1$s वा एक %2$s विस्तारका साथ PKCS#12 फाइलबाट स्थापना गर्न सक्नुहुन्छ।"</string>
<string name="allow_button" msgid="3030990695030371561">"चयन गर्नुहोस्"</string>
<string name="deny_button" msgid="3766539809121892584">"अस्वीकार गर्नुहोस्"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"प्रमाणपत्रहरू लोड गर्दै..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s ले प्रमाणपत्र छ कि छैन भन्ने कुरा जाँच्दै छ…"</string>
</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 1499c5a..6a18d46 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Je kunt certificaten installeren uit een PKCS#12-bestand met de extensie %1$s of %2$s dat zich bevindt in de externe opslag."</string>
<string name="allow_button" msgid="3030990695030371561">"Selecteren"</string>
<string name="deny_button" msgid="3766539809121892584">"Weigeren"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Certificaten laden..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s checkt op een certificaat…"</string>
</resources>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 46a9e88..ce63034 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"ଏକ୍ସଟର୍ନଲ୍ ଷ୍ଟୋରେଜ୍ରେ ଥିବା ଏକ PKCS#12 ଫାଇଲ୍ରୁ ଆପଣ ସର୍ଟିଫିକେଟ୍ଗୁଡ଼ିକ ଇନ୍ଷ୍ଟଲ୍ କରିପାରିବେ, ଯାହାର ଏକ୍ସଟେନ୍ସନ୍ ହେଉଛି %1$s କିମ୍ବା ଏକ %2$s।"</string>
<string name="allow_button" msgid="3030990695030371561">"ଚୟନ କରନ୍ତୁ"</string>
<string name="deny_button" msgid="3766539809121892584">"ଅଗ୍ରାହ୍ୟ"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"ସାର୍ଟିଫିକେଟ୍ଗୁଡ଼ିକ ଲୋଡ୍ ହେଉଛି..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s ଏକ ସାର୍ଟିଫିକେଟ୍ ପାଇଁ ଯାଞ୍ଚ କରୁଛି…"</string>
</resources>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index f71a630..5eace6a 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"ਤੁਸੀਂ ਬਾਹਰੀ ਸਟੋਰੇਜ ਵਿੱਚ ਸਥਿਤ ਇੱਕ %1$s ਜਾਂ ਇੱਕ %2$s ਐਕਸਟੈਂਸ਼ਨ ਨਾਲ ਇੱਕ PKCS#12 ਫ਼ਾਈਲ ਵਿੱਚੋਂ ਪ੍ਰਮਾਣ-ਪੱਤਰ ਸਥਾਪਤ ਕਰ ਸਕਦੇ ਹੋ।"</string>
<string name="allow_button" msgid="3030990695030371561">"ਚੁਣੋ"</string>
<string name="deny_button" msgid="3766539809121892584">"ਅਸਵੀਕਾਰ ਕਰੋ"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"ਪ੍ਰਮਾਣ-ਪੱਤਰਾਂ ਨੂੰ ਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s ਵੱਲੋਂ ਪ੍ਰਮਾਣ-ਪੱਤਰ ਦੀ ਜਾਂਚ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ…"</string>
</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index d1f86c0..218ffb7 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Możesz zainstalować certyfikaty z pliku PKCS#12 z rozszerzeniem %1$s lub %2$s, który znajduje się w pamięci zewnętrznej."</string>
<string name="allow_button" msgid="3030990695030371561">"Wybierz"</string>
<string name="deny_button" msgid="3766539809121892584">"Odmów"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Wczytuję certyfikaty…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"Aplikacja %s sprawdza dostępność certifikatu…"</string>
</resources>
diff --git a/res/values-pt-rBR/strings.xml b/res/values-pt-rBR/strings.xml
index 65a5d8a..4d6576e 100644
--- a/res/values-pt-rBR/strings.xml
+++ b/res/values-pt-rBR/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Você pode instalar certificados a partir de um arquivo PKCS#12 com a extensão %1$s ou %2$s armazenado externamente."</string>
<string name="allow_button" msgid="3030990695030371561">"Selecionar"</string>
<string name="deny_button" msgid="3766539809121892584">"Negar"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Carregando certificados…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s está procurando um certificado…"</string>
</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 8e78f58..d368795 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Pode instalar certificados a partir de um ficheiro PKCS#12 com uma extensão %1$s ou %2$s localizada no armazenamento externo."</string>
<string name="allow_button" msgid="3030990695030371561">"Selecionar"</string>
<string name="deny_button" msgid="3766539809121892584">"Recusar"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"A carregar os certificados…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s está a verificar a existência de um certificado…"</string>
</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 65a5d8a..4d6576e 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Você pode instalar certificados a partir de um arquivo PKCS#12 com a extensão %1$s ou %2$s armazenado externamente."</string>
<string name="allow_button" msgid="3030990695030371561">"Selecionar"</string>
<string name="deny_button" msgid="3766539809121892584">"Negar"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Carregando certificados…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s está procurando um certificado…"</string>
</resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 9c280e7..56f6f22 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Puteți instala certificate dintr-un fișier PKCS#12 cu o extensie %1$s sau %2$s care se află pe stocarea externă."</string>
<string name="allow_button" msgid="3030990695030371561">"Selectați"</string>
<string name="deny_button" msgid="3766539809121892584">"Refuzați"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Se încarcă certificatele..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s caută certificatul…"</string>
</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index b163f46..3899c78 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Можно установить сертификаты из файла PKCS#12 с расширением %1$s или %2$s, который находится во внешнем хранилище."</string>
<string name="allow_button" msgid="3030990695030371561">"Выбрать"</string>
<string name="deny_button" msgid="3766539809121892584">"Отклонить"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Загрузка сертификатов…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s проверяет наличие сертификата…"</string>
</resources>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index d4e66e9..5ebcacd 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"ඔබට බාහිර ආචයනයේ පිහිටුවා ඇති %1$s හෝ %2$s දිගුවක් සහිත PKCS#12 ගොනුවකින් සහතික ස්ථාපනය කළ හැක."</string>
<string name="allow_button" msgid="3030990695030371561">"තෝරන්න"</string>
<string name="deny_button" msgid="3766539809121892584">"තහනම්"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"සහතික පූරණය කරමින්…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s සහතිකයක් සඳහා පරීක්ෂා කරමින්…"</string>
</resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 2b2693e..40eb90f 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Certifikáty môžete inštalovať zo súboru PKCS#12 s príponou %1$s alebo %2$s, umiestneného v externom úložisku."</string>
<string name="allow_button" msgid="3030990695030371561">"Vybrať"</string>
<string name="deny_button" msgid="3766539809121892584">"Zamietnuť"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Načítavajú sa certifikáty…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s kontroluje certifikát…"</string>
</resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 9f67f7c..c5c19b7 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Namestite lahko potrdila iz datoteke PKCS#12 s pripono %1$s ali %2$s, ki je v zunanji shrambi."</string>
<string name="allow_button" msgid="3030990695030371561">"Izberi"</string>
<string name="deny_button" msgid="3766539809121892584">"Zavrni"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Nalaganje potrdil …"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s išče potrdilo …"</string>
</resources>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index e6e2d05..1e2644c 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Mund të instalosh certifikata nga skedari PKCS#12 që gjendet në hapësirën e jashtme ruajtëse, vetëm nëse ato bartin prapashtesat %1$s ose %2$s."</string>
<string name="allow_button" msgid="3030990695030371561">"Zgjidh"</string>
<string name="deny_button" msgid="3766539809121892584">"Refuzo"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Certifikatat po ngarkohen..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s po kontrollon për një certifikatë…"</string>
</resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index e037cbb..5aa2769 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Можете да инсталирате сертификате из PKCS#12 датотеке са екстензијом %1$s или %2$s који се налази у спољној меморији."</string>
<string name="allow_button" msgid="3030990695030371561">"Изабери"</string>
<string name="deny_button" msgid="3766539809121892584">"Одбиј"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Учитавају се сертификати…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s проверава да ли постоји сертификат…"</string>
</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index cabca96..15475b6 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Du kan installera certifikat från en PKCS#12-fil med filändelsen %1$s eller %2$s på en extern lagringsenhet."</string>
<string name="allow_button" msgid="3030990695030371561">"Välj"</string>
<string name="deny_button" msgid="3766539809121892584">"Neka"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Läser in certifikat …"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s söker efter ett certifikat …"</string>
</resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index f236a3c..553ced2 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Unaweza kusakinisha vyeti kutoka katika faili PKCS#12 kwa kirefusho %1$s au %2$s kilicho katika hifadhi ya nje."</string>
<string name="allow_button" msgid="3030990695030371561">"Chagua"</string>
<string name="deny_button" msgid="3766539809121892584">"Kataa"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Inapakia vyeti..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s inatafuta cheti…"</string>
</resources>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 687af73..4d79148 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"வெளிப்புறச் சேமிப்பிடத்தில் உள்ள %1$s அல்லது %2$s நீட்டிப்புடன் PKCS#12 கோப்பிலிருந்து நீங்கள் சான்றிதழ்களை நிறுவலாம்."</string>
<string name="allow_button" msgid="3030990695030371561">"தேர்ந்தெடு"</string>
<string name="deny_button" msgid="3766539809121892584">"நிராகரி"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"சான்றிதழ்களைக் காட்டுகிறது..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s சான்றிதழைத் தேடுகிறது…"</string>
</resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index afa25e7..d7e73c1 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -20,8 +20,8 @@
<string name="title_select_cert" msgid="3588447616418041699">"ప్రమాణపత్రాన్ని ఎంచుకోండి"</string>
<string name="requesting_application" msgid="1589142627467598421">"యాప్ %s ప్రమాణపత్రాన్ని అభ్యర్థించింది. ప్రమాణపత్రాన్ని ఎంచుకోవడం వలన ఇప్పుడు మరియు భవిష్యత్తులో సర్వర్లతో ఈ గుర్తింపును ఉపయోగించడానికి యాప్ అనుమతించబడుతుంది."</string>
<string name="requesting_server" msgid="5832565605998634370">"యాప్ అభ్యర్థిస్తున్న సర్వర్ను %sగా గుర్తించింది, కానీ మీరు యాప్ను విశ్వసిస్తే మాత్రమే సర్టిఫికెట్ కోసం యాప్నకు యాక్సెస్ను అందించాలి."</string>
- <string name="install_new_cert_message" msgid="4451971501142085495">"మీరు బాహ్య నిల్వలో ఉండే %1$s లేదా %2$s ఎక్స్టెన్షన్తో PKCS#12 ఫైల్ నుండి ప్రమాణపత్రాలను ఇన్స్టాల్ చేయవచ్చు."</string>
+ <string name="install_new_cert_message" msgid="4451971501142085495">"మీరు బాహ్య నిల్వలో ఉండే %1$s లేదా %2$s పొడిగింపుతో PKCS#12 ఫైల్ నుండి ప్రమాణపత్రాలను ఇన్స్టాల్ చేయవచ్చు."</string>
<string name="allow_button" msgid="3030990695030371561">"ఎంచుకోండి"</string>
<string name="deny_button" msgid="3766539809121892584">"తిరస్కరించు"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"సర్టిఫికెట్లు లోడ్ అవుతున్నాయి..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s సర్టిఫికెట్ కోసం చెక్ చేస్తున్నారు…"</string>
</resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 13c8558..d05adeb 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"คุณสามารถติดตั้งใบรับรองจากไฟล์ PKCS#12 ที่มีนามสกุล %1$s หรือ %2$s ที่อยู่ในที่จัดเก็บข้อมูลภายนอก"</string>
<string name="allow_button" msgid="3030990695030371561">"เลือก"</string>
<string name="deny_button" msgid="3766539809121892584">"ปฏิเสธ"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"กำลังโหลดใบรับรอง..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s กำลังตรวจสอบใบรับรอง…"</string>
</resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 3fa0f46..06fae2f 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Maaari kang mag-install ng mga certificate mula sa isang PKCS#12 file na may %1$s o %2$s na extension na matatagpuan sa external storage."</string>
<string name="allow_button" msgid="3030990695030371561">"Piliin"</string>
<string name="deny_button" msgid="3766539809121892584">"Tanggihan"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Nilo-load ang mga certificate..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"Tumitingin ang %s ng certificate…"</string>
</resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 3c745b2..6bc2b43 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Sertifikaları harici depolama biriminde bulunan %1$s veya %2$s uzantılı bir PKCS#12 dosyasından yükleyebilirsiniz."</string>
<string name="allow_button" msgid="3030990695030371561">"Seç"</string>
<string name="deny_button" msgid="3766539809121892584">"Reddet"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Sertifikalar yükleniyor…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s, sertifika olup olmadığına bakıyor…"</string>
</resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 1fa1b50..0226ccc 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Можна встановлювати сертифікати з файлу PKCS#12 із розширенням %1$s або %2$s, розміщеного в зовнішній пам’яті."</string>
<string name="allow_button" msgid="3030990695030371561">"Вибрати"</string>
<string name="deny_button" msgid="3766539809121892584">"Заборонити"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Завантаження сертифікатів…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s завантажує сертифікати…"</string>
</resources>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index e548886..ae63ea4 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"آپ بیرونی اسٹوریج میں موجود ایک %1$s یا %2$s ایکسٹنشن کے ساتھ ایک PKCS#12 فائل سے سرٹیفکیٹس انسٹال کر سکتے ہیں۔"</string>
<string name="allow_button" msgid="3030990695030371561">"منتخب کریں"</string>
<string name="deny_button" msgid="3766539809121892584">"مسترد کریں"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"سرٹیفیکیٹس لوڈ ہو رہے ہیں..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s سرٹیفیکیٹ کی جانچ کر رہی ہے…"</string>
</resources>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 605bf1a..a8c1deb 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Sertifikatlarni tashqi xotirada joylashgan %1$s yoki %2$s kengaytmali PKCS#12 faylidan o‘rnatishingiz mumkin."</string>
<string name="allow_button" msgid="3030990695030371561">"Tanlash"</string>
<string name="deny_button" msgid="3766539809121892584">"Rad etish"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Sertifikatlar yuklanmoqda..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s sertifikatni tekshirmoqda…"</string>
</resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 17a86f7..d219f62 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Bạn có thể cài đặt chứng chỉ từ tệp PKCS#12 với tiện ích mở rộng %1$s hoặc %2$s nằm trong bộ nhớ ngoài."</string>
<string name="allow_button" msgid="3030990695030371561">"Chọn"</string>
<string name="deny_button" msgid="3766539809121892584">"Từ chối"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Đang tải chứng chỉ..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s đang tìm chứng chỉ…"</string>
</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 7274056..485608e 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"您可以安装PKCS#12文件(位于外部存储设备中且扩展名为%1$s或%2$s)中的证书。"</string>
<string name="allow_button" msgid="3030990695030371561">"选择"</string>
<string name="deny_button" msgid="3766539809121892584">"拒绝"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"正在加载证书…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s正在检查证书…"</string>
</resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 988715a..890ff10 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"您可以從外部儲存裝置中副檔名為 %1$s 或 %2$s 的 PKCS#12 檔案安裝憑證。"</string>
<string name="allow_button" msgid="3030990695030371561">"選取"</string>
<string name="deny_button" msgid="3766539809121892584">"拒絕"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"正在載入憑證…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"%s 正在檢查憑證…"</string>
</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 57aae21..93c151f 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"你可以從外部儲存裝置中含有 %1$s 或 %2$s 擴充功能的 PKCS#12 檔案安裝憑證。"</string>
<string name="allow_button" msgid="3030990695030371561">"選取"</string>
<string name="deny_button" msgid="3766539809121892584">"拒絕"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"正在載入憑證…"</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"「%s」正在確認憑證…"</string>
</resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 2fd34d3..1a1148f 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -23,5 +23,5 @@
<string name="install_new_cert_message" msgid="4451971501142085495">"Ungafaka izitifiketi kusuka efayeleni le-PKCS#12 ngesandiso se-%1$s noma se-%2$s esibekwe esilondolozini sangaphandle."</string>
<string name="allow_button" msgid="3030990695030371561">"Khetha"</string>
<string name="deny_button" msgid="3766539809121892584">"Nqaba"</string>
- <string name="loading_certs_message" msgid="814752048905775439">"Ilayisha izitifiketi..."</string>
+ <string name="loading_certs_message" msgid="2152223341043639547">"I-%s ihlola isitifiketi…"</string>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 2da7102..7cc4b11 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -35,5 +35,5 @@
<string name="deny_button">Deny</string>
<!-- Text to show while the KeyChain activity is loading certificates. -->
- <string name="loading_certs_message">Loading certificates...</string>
+ <string name="loading_certs_message">%s is checking for a certificate…</string>
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 2e1081b..6135255 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -18,9 +18,10 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="KeyChainTransparent" parent="@android:style/Theme.DeviceDefault.DayNight">
+ <style name="KeyChainTransparent" parent="@style/Theme.AppCompat.DayNight.NoActionBar">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
+ <item name="android:backgroundDimEnabled">false</item>
</style>
</resources>
diff --git a/robotests/Android.bp b/robotests/Android.bp
index b6a6f4e..8ddbfc5 100644
--- a/robotests/Android.bp
+++ b/robotests/Android.bp
@@ -2,6 +2,10 @@
// KeyChain Robolectric test target. #
//############################################################
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
android_robolectric_test {
name: "KeyChainRoboTests",
diff --git a/robotests/src/com/android/keychain/AliasLoaderTest.java b/robotests/src/com/android/keychain/AliasLoaderTest.java
index 78c6c85..130edde 100644
--- a/robotests/src/com/android/keychain/AliasLoaderTest.java
+++ b/robotests/src/com/android/keychain/AliasLoaderTest.java
@@ -19,16 +19,12 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import android.security.Credentials;
-import android.security.KeyStore;
import android.util.Base64;
+
import com.android.keychain.internal.KeyInfoProvider;
-import java.util.ArrayList;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import javax.security.auth.x500.X500Principal;
+
+import com.google.common.collect.ImmutableList;
+
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -38,6 +34,22 @@
import org.robolectric.RuntimeEnvironment;
import org.robolectric.shadows.ShadowApplication;
+import java.io.ByteArrayInputStream;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.KeyStoreSpi;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import javax.security.auth.x500.X500Principal;
+
@RunWith(RobolectricTestRunner.class)
public final class AliasLoaderTest {
// Generated using:
@@ -125,9 +137,22 @@
private byte[] mECCertOne;
private byte[] mECCertTwo;
private ArrayList<byte[]> mIssuers;
+ private KeyStoreSpi mKeyStoreSpi;
+ private KeyStore mKeyStore;
+ private KeyInfoProvider mInfoProvider;
+
+ private Certificate toCertificate(byte[] bytes) throws CertificateException {
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ return cf.generateCertificate(new ByteArrayInputStream(bytes));
+ }
+
+ private Certificate[] toCertificateChain(byte[] bytes) throws CertificateException {
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ return new Certificate[]{cf.generateCertificate(new ByteArrayInputStream(bytes))};
+ }
@Before
- public void setUp() {
+ public void setUp() throws Exception {
mRSACertOne = Base64.decode(SELF_SIGNED_RSA_CERT_1_B64, Base64.DEFAULT);
mRSACertTwo = Base64.decode(SELF_SIGNED_RSA_CERT_2_B64, Base64.DEFAULT);
mECCertOne = Base64.decode(SELF_SIGNED_EC_CERT_1_B64, Base64.DEFAULT);
@@ -143,18 +168,22 @@
mDummyChecker = mock(KeyChainActivity.CertificateParametersFilter.class);
when(mDummyChecker.shouldPresentCertificate(Mockito.anyString())).thenReturn(true);
+
+ mKeyStoreSpi = mock(KeyStoreSpi.class);
+ mKeyStore = new KeyStore(mKeyStoreSpi, null, "test") {};
+ mKeyStore.load(null);
+ mInfoProvider = mock(KeyInfoProvider.class);
}
@Test
public void testAliasLoader_loadsAllAliases()
throws InterruptedException, ExecutionException, CancellationException,
- TimeoutException {
- KeyStore keyStore = mock(KeyStore.class);
- when(keyStore.list(Credentials.USER_PRIVATE_KEY)).thenReturn(new String[] {"b", "c", "a"});
+ TimeoutException {
+ prepareKeyStoreWithAliases(ImmutableList.of("b", "c", "a"));
KeyChainActivity.AliasLoader loader =
new KeyChainActivity.AliasLoader(
- keyStore,
+ mKeyStore,
RuntimeEnvironment.application,
mDummyInfoProvider,
mDummyChecker);
@@ -173,12 +202,11 @@
public void testAliasLoader_copesWithNoAliases()
throws InterruptedException, ExecutionException, CancellationException,
TimeoutException {
- KeyStore keyStore = mock(KeyStore.class);
- when(keyStore.list(Credentials.USER_PRIVATE_KEY)).thenReturn(null);
+ when(mKeyStoreSpi.engineAliases()).thenReturn(Collections.enumeration(ImmutableList.of()));
KeyChainActivity.AliasLoader loader =
new KeyChainActivity.AliasLoader(
- keyStore,
+ mKeyStore,
RuntimeEnvironment.application,
mDummyInfoProvider,
mDummyChecker);
@@ -194,17 +222,14 @@
public void testAliasLoader_filtersNonUserSelectableAliases()
throws InterruptedException, ExecutionException, CancellationException,
TimeoutException {
- KeyStore keyStore = mock(KeyStore.class);
- when(keyStore.list(Credentials.USER_PRIVATE_KEY)).thenReturn(new String[] {"a", "b", "c"});
-
- KeyInfoProvider infoProvider = mock(KeyInfoProvider.class);
- when(infoProvider.isUserSelectable("a")).thenReturn(false);
- when(infoProvider.isUserSelectable("b")).thenReturn(true);
- when(infoProvider.isUserSelectable("c")).thenReturn(false);
+ prepareKeyStoreWithAliases(ImmutableList.of("b", "c", "a"));
+ when(mInfoProvider.isUserSelectable("a")).thenReturn(false);
+ when(mInfoProvider.isUserSelectable("b")).thenReturn(true);
+ when(mInfoProvider.isUserSelectable("c")).thenReturn(false);
KeyChainActivity.AliasLoader loader =
new KeyChainActivity.AliasLoader(
- keyStore, RuntimeEnvironment.application, infoProvider, mDummyChecker);
+ mKeyStore, RuntimeEnvironment.application, mInfoProvider, mDummyChecker);
loader.execute();
ShadowApplication.runBackgroundTasks();
@@ -217,16 +242,12 @@
@Test
public void testAliasLoader_filtersAliasesWithNonConformingParameters()
throws InterruptedException, ExecutionException, CancellationException,
- TimeoutException {
- KeyStore keyStore = mock(KeyStore.class);
- when(keyStore.list(Credentials.USER_PRIVATE_KEY))
- .thenReturn(new String[] {"a", "b", "c", "d"});
-
- KeyInfoProvider infoProvider = mock(KeyInfoProvider.class);
- when(infoProvider.isUserSelectable("a")).thenReturn(true);
- when(infoProvider.isUserSelectable("b")).thenReturn(true);
- when(infoProvider.isUserSelectable("c")).thenReturn(false);
- when(infoProvider.isUserSelectable("d")).thenReturn(false);
+ TimeoutException, KeyStoreException {
+ prepareKeyStoreWithAliases(ImmutableList.of("a", "b", "c", "d"));
+ when(mInfoProvider.isUserSelectable("a")).thenReturn(true);
+ when(mInfoProvider.isUserSelectable("b")).thenReturn(true);
+ when(mInfoProvider.isUserSelectable("c")).thenReturn(false);
+ when(mInfoProvider.isUserSelectable("d")).thenReturn(false);
KeyChainActivity.CertificateParametersFilter checker =
mock(KeyChainActivity.CertificateParametersFilter.class);
@@ -241,7 +262,7 @@
KeyChainActivity.AliasLoader loader =
new KeyChainActivity.AliasLoader(
- keyStore, RuntimeEnvironment.application, infoProvider, checker);
+ mKeyStore, RuntimeEnvironment.application, mInfoProvider, checker);
loader.execute();
ShadowApplication.runBackgroundTasks();
@@ -251,40 +272,55 @@
Assert.assertEquals("a", result.getItem(0));
}
- private KeyStore prepareKeyStoreWithCertificates() {
- KeyStore keyStore = mock(KeyStore.class);
- when(keyStore.get(Credentials.USER_CERTIFICATE + "rsa1")).thenReturn(mRSACertOne);
- when(keyStore.get(Credentials.USER_CERTIFICATE + "ec1")).thenReturn(mECCertOne);
- when(keyStore.get(Credentials.USER_CERTIFICATE + "rsa2")).thenReturn(mRSACertTwo);
- when(keyStore.get(Credentials.USER_CERTIFICATE + "ec2")).thenReturn(mECCertTwo);
+ private void prepareKeyStoreWithAliases(ImmutableList<String> aliases) {
+ when(mKeyStoreSpi.engineAliases()).thenReturn(Collections.enumeration(aliases));
+ for (int i = 0; i < aliases.size(); i++) {
+ when(mKeyStoreSpi.engineIsKeyEntry(aliases.get(i))).thenReturn(true);
+ }
+ }
- return keyStore;
+ private void prepareKeyStoreWithCertificates() throws CertificateException {
+ when(mKeyStoreSpi.engineGetCertificate("rsa1")).thenReturn(toCertificate(mRSACertOne));
+ when(mKeyStoreSpi.engineGetCertificate("ec1")).thenReturn(toCertificate(mECCertOne));
+ when(mKeyStoreSpi.engineGetCertificate("rsa2")).thenReturn(toCertificate(mRSACertTwo));
+ when(mKeyStoreSpi.engineGetCertificate("ec2")).thenReturn(toCertificate(mECCertTwo));
+
+ when(mKeyStoreSpi.engineGetCertificateChain("rsa1"))
+ .thenReturn(toCertificateChain(mRSACertOne));
+ when(mKeyStoreSpi.engineGetCertificateChain("ec1"))
+ .thenReturn(toCertificateChain(mECCertOne));
+ when(mKeyStoreSpi.engineGetCertificateChain("rsa2"))
+ .thenReturn(toCertificateChain(mRSACertTwo));
+ when(mKeyStoreSpi.engineGetCertificateChain("ec2"))
+ .thenReturn(toCertificateChain(mECCertTwo));
}
@Test
- public void testCertificateParametersFilter_filtersByKey() throws CancellationException {
- KeyStore keyStore = prepareKeyStoreWithCertificates();
+ public void testCertificateParametersFilter_filtersByKey()
+ throws CancellationException, CertificateException {
+ prepareKeyStoreWithCertificates();
KeyChainActivity.CertificateParametersFilter ec_checker =
new KeyChainActivity.CertificateParametersFilter(
- keyStore, new String[] {"EC"}, new ArrayList<byte[]>());
+ mKeyStore, new String[] {"EC"}, new ArrayList<byte[]>());
Assert.assertFalse(ec_checker.shouldPresentCertificate("rsa1"));
Assert.assertTrue(ec_checker.shouldPresentCertificate("ec1"));
KeyChainActivity.CertificateParametersFilter rsa_and_ec_checker =
new KeyChainActivity.CertificateParametersFilter(
- keyStore, new String[] {"EC", "RSA"}, new ArrayList<byte[]>());
+ mKeyStore, new String[] {"EC", "RSA"}, new ArrayList<byte[]>());
Assert.assertTrue(rsa_and_ec_checker.shouldPresentCertificate("rsa1"));
Assert.assertTrue(rsa_and_ec_checker.shouldPresentCertificate("ec1"));
}
@Test
- public void testCertificateParametersFilter_filtersByIssuer() throws CancellationException {
- KeyStore keyStore = prepareKeyStoreWithCertificates();
+ public void testCertificateParametersFilter_filtersByIssuer()
+ throws CancellationException, CertificateException {
+ prepareKeyStoreWithCertificates();
KeyChainActivity.CertificateParametersFilter issuer_checker =
new KeyChainActivity.CertificateParametersFilter(
- keyStore, new String[] {}, mIssuers);
+ mKeyStore, new String[] {}, mIssuers);
Assert.assertTrue(issuer_checker.shouldPresentCertificate("rsa1"));
Assert.assertTrue(issuer_checker.shouldPresentCertificate("ec1"));
Assert.assertFalse(issuer_checker.shouldPresentCertificate("rsa2"));
@@ -293,13 +329,12 @@
@Test
public void testCertificateParametersFilter_filtersByIssuerAndKey()
- throws InterruptedException, ExecutionException, CancellationException,
- TimeoutException {
- KeyStore keyStore = prepareKeyStoreWithCertificates();
+ throws CancellationException, CertificateException {
+ prepareKeyStoreWithCertificates();
KeyChainActivity.CertificateParametersFilter issuer_checker =
new KeyChainActivity.CertificateParametersFilter(
- keyStore, new String[] {"EC"}, mIssuers);
+ mKeyStore, new String[] {"EC"}, mIssuers);
Assert.assertFalse(issuer_checker.shouldPresentCertificate("rsa1"));
Assert.assertTrue(issuer_checker.shouldPresentCertificate("ec1"));
Assert.assertFalse(issuer_checker.shouldPresentCertificate("rsa2"));
diff --git a/robotests/src/com/android/keychain/KeyChainServiceRoboTest.java b/robotests/src/com/android/keychain/KeyChainServiceRoboTest.java
index 8594e56..6392af4 100644
--- a/robotests/src/com/android/keychain/KeyChainServiceRoboTest.java
+++ b/robotests/src/com/android/keychain/KeyChainServiceRoboTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
@@ -33,10 +34,11 @@
import android.app.admin.SecurityLog;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.security.AppUriAuthenticationPolicy;
import android.security.IKeyChainService;
import com.android.org.conscrypt.TrustedCertificateStore;
-import com.android.keychain.ShadowKeyStore;
import org.junit.Before;
import org.junit.Test;
@@ -52,6 +54,7 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
@@ -61,9 +64,11 @@
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {
ShadowTrustedCertificateStore.class,
- ShadowKeyStore.class
})
public final class KeyChainServiceRoboTest {
+
+ private static final String DEFAULT_KEYSTORE_TYPE = "BKS";
+
private IKeyChainService.Stub mKeyChain;
@Mock
@@ -100,6 +105,15 @@
private static final String NON_EXISTING_ALIAS = "alias-does-not-exist-1";
+ private static final String TEST_PACKAGE_NAME_1 = "com.android.test";
+ private static final Uri TEST_URI_1 = Uri.parse("test.com");
+ private static final String TEST_ALIAS_1 = "testAlias";
+ private static final String CREDENTIAL_MANAGER_PACKAGE = "com.android.cred.mng.pkg";
+ private static final AppUriAuthenticationPolicy AUTHENTICATION_POLICY =
+ new AppUriAuthenticationPolicy.Builder()
+ .addAppAndUriMapping(TEST_PACKAGE_NAME_1, TEST_URI_1, TEST_ALIAS_1)
+ .build();
+
private X509Certificate mCert;
private String mSubject;
private ShadowPackageManager mShadowPackageManager;
@@ -116,9 +130,13 @@
mShadowPackageManager = shadowOf(packageManager);
final ServiceController<KeyChainService> serviceController =
- Robolectric.buildService(KeyChainService.class).create().bind();
+ Robolectric.buildService(KeyChainService.class);
final KeyChainService service = serviceController.get();
service.setInjector(mockInjector);
+ doReturn(KeyStore.getInstance(DEFAULT_KEYSTORE_TYPE))
+ .when(mockInjector).getKeyStoreInstance();
+ serviceController.create().bind();
+
final Intent intent = new Intent(IKeyChainService.class.getName());
mKeyChain = (IKeyChainService.Stub) service.onBind(intent);
}
@@ -239,6 +257,83 @@
assertThat(certificate).isNull();
}
+ @Test
+ public void testHasCredentialManagementApp_noManagementApp_returnsFalse() throws Exception {
+ setUpSystemCaller();
+ assertFalse(mKeyChain.hasCredentialManagementApp());
+ }
+
+ @Test
+ public void testGetCredentialManagementAppPackageName_noManagementApp_returnsNull()
+ throws Exception {
+ setUpSystemCaller();
+ assertThat(mKeyChain.getCredentialManagementAppPackageName()).isNull();
+ }
+
+ @Test
+ public void testGetCredentialManagementAppPolicy_noManagementApp_returnsNull()
+ throws Exception {
+ setUpSystemCaller();
+ assertThat(mKeyChain.getCredentialManagementAppPolicy()).isNull();
+ }
+
+ @Test
+ public void testGetPredefinedAliasForPackageAndUri_noManagementApp_returnsNull()
+ throws Exception {
+ setUpSystemCaller();
+ assertThat(mKeyChain.getPredefinedAliasForPackageAndUri(TEST_PACKAGE_NAME_1,
+ TEST_URI_1)).isNull();
+ }
+
+ @Test
+ public void testHasCredentialManagement_hasManagementApp_returnsTrue() throws Exception {
+ setUpSystemCaller();
+ mKeyChain.setCredentialManagementApp(CREDENTIAL_MANAGER_PACKAGE, AUTHENTICATION_POLICY);
+
+ assertTrue(mKeyChain.hasCredentialManagementApp());
+ }
+
+ @Test
+ public void testGetCredentialManagementAppPackageName_hasManagementApp_returnsPackageName()
+ throws Exception {
+ setUpSystemCaller();
+ mKeyChain.setCredentialManagementApp(CREDENTIAL_MANAGER_PACKAGE, AUTHENTICATION_POLICY);
+
+ assertThat(mKeyChain.getCredentialManagementAppPackageName())
+ .isEqualTo(CREDENTIAL_MANAGER_PACKAGE);
+ }
+
+ @Test
+ public void testGetCredentialManagementAppPolicy_hasManagementApp_returnsPolicy()
+ throws Exception {
+ setUpSystemCaller();
+ mKeyChain.setCredentialManagementApp(CREDENTIAL_MANAGER_PACKAGE, AUTHENTICATION_POLICY);
+
+ assertThat(mKeyChain.getCredentialManagementAppPolicy()).isEqualTo(AUTHENTICATION_POLICY);
+ }
+
+ @Test
+ public void testGetPredefinedAliasForPackageAndUri_hasManagementApp_returnsCorrectAlias()
+ throws Exception {
+ setUpSystemCaller();
+ mKeyChain.setCredentialManagementApp(CREDENTIAL_MANAGER_PACKAGE, AUTHENTICATION_POLICY);
+
+ assertThat(mKeyChain.getPredefinedAliasForPackageAndUri(TEST_PACKAGE_NAME_1, TEST_URI_1))
+ .isEqualTo(TEST_ALIAS_1);
+ }
+
+ @Test
+ public void testRemoveCredentialManagementApp_hasManagementApp_removesManagementApp()
+ throws Exception {
+ setUpSystemCaller();
+
+ mKeyChain.removeCredentialManagementApp();
+
+ assertFalse(mKeyChain.hasCredentialManagementApp());
+ assertThat(mKeyChain.getCredentialManagementAppPackageName()).isNull();
+ assertThat(mKeyChain.getCredentialManagementAppPolicy()).isNull();
+ }
+
private void setUpLoggingAndAccess(boolean loggingEnabled) {
doReturn(loggingEnabled).when(mockInjector).isSecurityLoggingEnabled();
@@ -246,6 +341,10 @@
setUpCaller(1000, "android.uid.system:1000");
}
+ private void setUpSystemCaller() {
+ setUpCaller(1000, "android.uid.system:1000");
+ }
+
private void setUpCaller(int uid, String packageName) {
doReturn(uid).when(mockInjector).getCallingUid();
mShadowPackageManager.setNameForUid(uid, packageName);
diff --git a/robotests/src/com/android/keychain/ShadowKeyStore.java b/robotests/src/com/android/keychain/ShadowKeyStore.java
deleted file mode 100644
index 1a7a2b3..0000000
--- a/robotests/src/com/android/keychain/ShadowKeyStore.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.keychain;
-
-import android.security.KeyStore;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-
-import java.util.List;
-
-@Implements(KeyStore.class)
-public class ShadowKeyStore {
- @Implementation
- public String[] list(String prefix) {
- return new String[0];
- }
-}
diff --git a/robotests/src/com/android/keychain/internal/GrantsDatabaseTest.java b/robotests/src/com/android/keychain/internal/GrantsDatabaseTest.java
index e5212da..4cf552f 100644
--- a/robotests/src/com/android/keychain/internal/GrantsDatabaseTest.java
+++ b/robotests/src/com/android/keychain/internal/GrantsDatabaseTest.java
@@ -369,4 +369,18 @@
Assert.assertTrue(mGrantsDB.isUserSelectable(alias));
}
}
+
+ @Test
+ public void testGetGrants_empty() {
+ Assert.assertArrayEquals(new int[0], mGrantsDB.getGrants(DUMMY_ALIAS));
+ }
+
+ @Test
+ public void testGetGrants_nonEmpty() {
+ mGrantsDB.setGrant(DUMMY_UID, DUMMY_ALIAS, true);
+ mGrantsDB.setGrant(DUMMY_UID + 1, DUMMY_ALIAS, true);
+
+ Assert.assertArrayEquals(
+ new int[]{DUMMY_UID, DUMMY_UID + 1}, mGrantsDB.getGrants(DUMMY_ALIAS));
+ }
}
diff --git a/src/com/android/keychain/KeyChainActivity.java b/src/com/android/keychain/KeyChainActivity.java
index 078e377..105dc6a 100644
--- a/src/com/android/keychain/KeyChainActivity.java
+++ b/src/com/android/keychain/KeyChainActivity.java
@@ -17,25 +17,29 @@
package com.android.keychain;
import android.annotation.NonNull;
-import android.app.Activity;
import android.app.AlertDialog;
import android.app.PendingIntent;
+import android.app.admin.DevicePolicyEventLogger;
+import android.app.admin.DevicePolicyManager;
import android.app.admin.IDevicePolicyManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.net.Uri;
import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.security.Credentials;
+import android.os.UserManager;
import android.security.IKeyChainAliasCallback;
import android.security.KeyChain;
-import android.security.KeyStore;
+import android.stats.devicepolicy.DevicePolicyEnums;
import android.util.Log;
-import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -45,29 +49,46 @@
import android.widget.RadioButton;
import android.widget.TextView;
+import androidx.appcompat.app.AppCompatActivity;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.keychain.internal.KeyInfoProvider;
-import com.android.org.bouncycastle.asn1.x509.X509Name;
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
+import com.google.android.material.snackbar.Snackbar;
+
+import org.bouncycastle.asn1.x509.X509Name;
+
import java.io.IOException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.Certificate;
import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import javax.security.auth.x500.X500Principal;
-public class KeyChainActivity extends Activity {
+public class KeyChainActivity extends AppCompatActivity {
private static final String TAG = "KeyChain";
+ // The amount of time to delay showing a snackbar. If the alias is received before the snackbar
+ // is shown, the activity will finish. If the certificate selection dialog is shown before the
+ // snackbar, no snackbar will be shown.
+ private static final long SNACKBAR_DELAY_TIME = 2000;
+ // The minimum amount of time to display a snackbar while loading certificates.
+ private static final long SNACKBAR_MIN_TIME = 1000;
+
private int mSenderUid;
+ private String mSenderPackageName;
private PendingIntent mSender;
@@ -75,11 +96,39 @@
// get do file I/O in the remote keystore process and while they
// do not cause StrictMode violations, they logically should not
// be done on the UI thread.
- private KeyStore mKeyStore = KeyStore.getInstance();
+ private final KeyStore mKeyStore = getKeyStore();
- // A dialog to show the user while the KeyChain Activity is loading the
- // certificates.
- AlertDialog mLoadingDialog;
+ private static KeyStore getKeyStore() {
+ try {
+ final KeyStore keystore = KeyStore.getInstance("AndroidKeyStore");
+ keystore.load(null);
+ return keystore;
+ } catch (KeyStoreException | IOException | NoSuchAlgorithmException
+ | CertificateException e) {
+ Log.e(TAG, "Error opening AndroidKeyStore.", e);
+ throw new RuntimeException("Error opening AndroidKeyStore.", e);
+ }
+ }
+
+ // A snackbar to show the user while the KeyChain Activity is loading the certificates.
+ private Snackbar mSnackbar;
+
+ // A remote service may call {@link android.security.KeyChain#choosePrivateKeyAlias} multiple
+ // times, which will result in multiple intents being sent to KeyChainActivity. The time of the
+ // first received intent is recorded in order to ensure the snackbar is displayed for a
+ // minimum amount of time after receiving the first intent.
+ private long mFirstIntentReceivedTimeMillis = 0L;
+
+ private ExecutorService executor = Executors.newSingleThreadExecutor();
+ private Handler handler = new Handler(Looper.getMainLooper());
+ private final Runnable mFinishActivity = KeyChainActivity.this::finish;
+ private final Runnable mShowSnackBar = this::showSnackBar;
+
+ @Override
+ protected void onCreate(Bundle savedState) {
+ super.onCreate(savedState);
+ setContentView(R.layout.keychain_activity);
+ }
@Override public void onResume() {
super.onResume();
@@ -91,8 +140,12 @@
return;
}
try {
+ // getTargetPackage guarantees that the returned string is
+ // supplied by the system, so that an application can not
+ // spoof its package.
+ mSenderPackageName = mSender.getIntentSender().getTargetPackage();
mSenderUid = getPackageManager().getPackageInfo(
- mSender.getIntentSender().getTargetPackage(), 0).applicationInfo.uid;
+ mSenderPackageName, 0).applicationInfo.uid;
} catch (PackageManager.NameNotFoundException e) {
// if unable to find the sender package info bail,
// we need to identify the app to the user securely.
@@ -103,14 +156,27 @@
chooseCertificate();
}
- private void showLoadingDialog() {
- final Context themedContext = new ContextThemeWrapper(
- this, com.android.internal.R.style.Theme_Translucent_NoTitleBar);
- mLoadingDialog = new AlertDialog.Builder(themedContext)
- .setTitle(R.string.app_name)
- .setMessage(R.string.loading_certs_message)
- .create();
- mLoadingDialog.show();
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ handler.removeCallbacks(mFinishActivity);
+ }
+
+ private void showSnackBar() {
+ mFirstIntentReceivedTimeMillis = System.currentTimeMillis();
+ mSnackbar = Snackbar.make(findViewById(R.id.container),
+ String.format(getResources().getString(R.string.loading_certs_message),
+ getApplicationLabel()), Snackbar.LENGTH_INDEFINITE);
+ mSnackbar.show();
+ }
+
+ private void finishSnackBar() {
+ if (mSnackbar != null) {
+ mSnackbar.dismiss();
+ mSnackbar = null;
+ } else {
+ handler.removeCallbacks(mShowSnackBar);
+ }
}
private void chooseCertificate() {
@@ -182,38 +248,99 @@
finish(null);
return;
}
- runOnUiThread(new Runnable() {
- @Override public void run() {
- if (mLoadingDialog != null) {
- mLoadingDialog.dismiss();
- mLoadingDialog = null;
- }
- displayCertChooserDialog(certAdapter);
- }
+ runOnUiThread(() -> {
+ finishSnackBar();
+ displayCertChooserDialog(certAdapter);
});
}
};
- // Show a dialog to the user to indicate long-running task.
- showLoadingDialog();
- // Give a profile or device owner the chance to intercept the request, if a private key
- // access listener is registered with the DevicePolicyManagerService.
- IDevicePolicyManager devicePolicyManager = IDevicePolicyManager.Stub.asInterface(
- ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
-
+ // Show a snackbar to the user to indicate long-running task.
+ if (mSnackbar == null) {
+ handler.postDelayed(mShowSnackBar, SNACKBAR_DELAY_TIME);
+ }
Uri uri = getIntent().getParcelableExtra(KeyChain.EXTRA_URI);
String alias = getIntent().getStringExtra(KeyChain.EXTRA_ALIAS);
- try {
- devicePolicyManager.choosePrivateKeyAlias(mSenderUid, uri, alias, callback);
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to request alias from DevicePolicyManager", e);
- // Proceed without a suggested alias.
+
+ if (isManagedDevice()) {
+ // Give a profile or device owner the chance to intercept the request, if a private key
+ // access listener is registered with the DevicePolicyManagerService.
+ IDevicePolicyManager devicePolicyManager = IDevicePolicyManager.Stub.asInterface(
+ ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
try {
- callback.alias(null);
- } catch (RemoteException shouldNeverHappen) {
- finish(null);
+ devicePolicyManager.choosePrivateKeyAlias(mSenderUid, uri, alias, callback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to request alias from DevicePolicyManager", e);
+ // Proceed without a suggested alias.
+ try {
+ callback.alias(null);
+ } catch (RemoteException shouldNeverHappen) {
+ finish(null);
+ }
+ }
+ } else {
+ // If the device is unmanaged, check whether the credential management app has provided
+ // an alias for the given uri and calling package name.
+ getAliasFromCredentialManagementApp(uri, callback);
+ }
+ }
+
+ private boolean isManagedDevice() {
+ DevicePolicyManager devicePolicyManager = getSystemService(DevicePolicyManager.class);
+ return devicePolicyManager.getDeviceOwner() != null
+ || devicePolicyManager.getProfileOwner() != null
+ || hasManagedProfile();
+ }
+
+ private boolean hasManagedProfile() {
+ UserManager userManager = getSystemService(UserManager.class);
+ for (final UserInfo userInfo : userManager.getProfiles(getUserId())) {
+ if (userInfo.isManagedProfile()) {
+ return true;
}
}
+ return false;
+ }
+
+ private void getAliasFromCredentialManagementApp(Uri uri,
+ IKeyChainAliasCallback.Stub callback) {
+ executor.execute(() -> {
+ try (KeyChain.KeyChainConnection keyChainConnection = KeyChain.bind(this)) {
+ String chosenAlias = null;
+ if (keyChainConnection.getService().hasCredentialManagementApp()) {
+ Log.i(TAG, "There is a credential management app on the device. "
+ + "Looking for an alias in the policy.");
+ chosenAlias = keyChainConnection.getService()
+ .getPredefinedAliasForPackageAndUri(mSenderPackageName, uri);
+ if (chosenAlias != null) {
+ keyChainConnection.getService().setGrant(mSenderUid, chosenAlias, true);
+ Log.w(TAG, String.format("Selected alias %s from the "
+ + "credential management app's policy", chosenAlias));
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums
+ .CREDENTIAL_MANAGEMENT_APP_CREDENTIAL_FOUND_IN_POLICY)
+ .write();
+ } else {
+ Log.i(TAG, "No alias provided from the credential management app");
+ }
+ }
+ callback.alias(chosenAlias);
+ } catch (InterruptedException | RemoteException e) {
+ Log.e(TAG, "Unable to request find predefined alias from credential "
+ + "management app policy");
+ // Proceed without a suggested alias.
+ try {
+ callback.alias(null);
+ } catch (RemoteException shouldNeverHappen) {
+ finish(null);
+ } finally {
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums
+ .CREDENTIAL_MANAGEMENT_APP_POLICY_LOOKUP_FAILED)
+ .write();
+ }
+ }
+ });
}
@VisibleForTesting
@@ -297,8 +424,8 @@
private final KeyInfoProvider mInfoProvider;
private final CertificateParametersFilter mCertificateFilter;
- public AliasLoader(KeyStore keyStore, Context context, KeyInfoProvider infoProvider,
- CertificateParametersFilter certificateFilter) {
+ public AliasLoader(KeyStore keyStore, Context context,
+ KeyInfoProvider infoProvider, CertificateParametersFilter certificateFilter) {
mKeyStore = keyStore;
mContext = context;
mInfoProvider = infoProvider;
@@ -306,10 +433,19 @@
}
@Override protected CertificateAdapter doInBackground(Void... params) {
- String[] aliasArray = mKeyStore.list(Credentials.USER_PRIVATE_KEY);
- List<String> rawAliasList = ((aliasArray == null)
- ? Collections.<String>emptyList()
- : Arrays.asList(aliasArray));
+ final List<String> rawAliasList = new ArrayList<>();
+ try {
+ final Enumeration<String> aliases = mKeyStore.aliases();
+ while (aliases.hasMoreElements()) {
+ final String alias = aliases.nextElement();
+ if (mKeyStore.isKeyEntry(alias)) {
+ rawAliasList.add(alias);
+ }
+ }
+ } catch (KeyStoreException e) {
+ Log.e(TAG, "Error while loading entries from keystore. "
+ + "List may be empty or incomplete.");
+ }
return new CertificateAdapter(mKeyStore, mContext,
rawAliasList.stream().filter(mInfoProvider::isUserSelectable)
@@ -389,20 +525,8 @@
}
});
- // getTargetPackage guarantees that the returned string is
- // supplied by the system, so that an application can not
- // spoof its package.
- String pkg = mSender.getIntentSender().getTargetPackage();
- PackageManager pm = getPackageManager();
- CharSequence applicationLabel;
- try {
- applicationLabel = pm.getApplicationLabel(pm.getApplicationInfo(pkg, 0)).toString();
- } catch (PackageManager.NameNotFoundException e) {
- applicationLabel = pkg;
- }
- String appMessage = String.format(res.getString(R.string.requesting_application),
- applicationLabel);
- String contextMessage = appMessage;
+ String contextMessage = String.format(res.getString(R.string.requesting_application),
+ getApplicationLabel());
Uri uri = getIntent().getParcelableExtra(KeyChain.EXTRA_URI);
if (uri != null) {
String hostMessage = String.format(res.getString(R.string.requesting_server),
@@ -431,6 +555,15 @@
dialog.show();
}
+ private String getApplicationLabel() {
+ PackageManager pm = getPackageManager();
+ try {
+ return pm.getApplicationLabel(pm.getApplicationInfo(mSenderPackageName, 0)).toString();
+ } catch (PackageManager.NameNotFoundException e) {
+ return mSenderPackageName;
+ }
+ }
+
@VisibleForTesting
static class CertificateAdapter extends BaseAdapter {
private final List<String> mAliases;
@@ -532,10 +665,6 @@
}
private void finish(String alias, boolean isAliasFromPolicy) {
- if (mLoadingDialog != null) {
- mLoadingDialog.dismiss();
- mLoadingDialog = null;
- }
if (alias == null || alias.equals(KeyChain.KEY_ALIAS_SELECTION_DENIED)) {
alias = null;
setResult(RESULT_CANCELED);
@@ -551,7 +680,7 @@
new ResponseSender(keyChainAliasResponse, alias, isAliasFromPolicy).execute();
return;
}
- finish();
+ finishActivity();
}
private class ResponseSender extends AsyncTask<Void, Void, Void> {
@@ -598,7 +727,20 @@
return null;
}
@Override protected void onPostExecute(Void unused) {
+ finishActivity();
+ }
+ }
+
+ private void finishActivity() {
+ long timeElapsedSinceFirstIntent =
+ System.currentTimeMillis() - mFirstIntentReceivedTimeMillis;
+ if (mFirstIntentReceivedTimeMillis == 0L
+ || timeElapsedSinceFirstIntent > SNACKBAR_MIN_TIME) {
+ finishSnackBar();
finish();
+ } else {
+ long remainingTimeToShowSnackBar = SNACKBAR_MIN_TIME - timeElapsedSinceFirstIntent;
+ handler.postDelayed(mFinishActivity, remainingTimeToShowSnackBar);
}
}
@@ -607,33 +749,51 @@
}
private static X509Certificate loadCertificate(KeyStore keyStore, String alias) {
- byte[] bytes = keyStore.get(Credentials.USER_CERTIFICATE + alias);
- if (bytes == null) {
- Log.i(TAG, String.format("Missing user certificate for key alias %s", alias));
- return null;
- }
- InputStream in = new ByteArrayInputStream(bytes);
+ final Certificate cert;
try {
- CertificateFactory cf = CertificateFactory.getInstance("X.509");
- return (X509Certificate)cf.generateCertificate(in);
- } catch (CertificateException ignored) {
- Log.w(TAG, "Error generating certificate", ignored);
+ if (keyStore.isCertificateEntry(alias)) {
+ return null;
+ }
+ cert = keyStore.getCertificate(alias);
+ } catch (KeyStoreException e) {
+ Log.e(TAG, String.format("Error trying to retrieve certificate for \"%s\".", alias), e);
return null;
}
+ if (cert != null) {
+ if (cert instanceof X509Certificate) {
+ return (X509Certificate) cert;
+ } else {
+ Log.w(TAG, String.format("Certificate associated with alias \"%s\" is not X509.",
+ alias));
+ }
+ }
+ return null;
}
- private static List<X509Certificate> loadCertificateChain(KeyStore keyStore, String alias) {
- byte[] chainBytes = keyStore.get(Credentials.CA_CERTIFICATE + alias);
- if (chainBytes == null) {
- Log.i(TAG, String.format("Missing certificate chain for key alias %s", alias));
- return Collections.emptyList();
- }
-
+ private static List<X509Certificate> loadCertificateChain(KeyStore keyStore,
+ String alias) {
+ final Certificate[] certs;
+ final boolean isCertificateEntry;
try {
- return Credentials.convertFromPem(chainBytes);
- } catch (IOException | CertificateException e) {
- Log.w(TAG, String.format("Error parsing certificate chain for alias %s", alias), e);
+ isCertificateEntry = keyStore.isCertificateEntry(alias);
+ certs = keyStore.getCertificateChain(alias);
+ } catch (KeyStoreException e) {
+ Log.e(TAG, String.format("Error trying to retrieve certificate chain for \"%s\".",
+ alias), e);
return Collections.emptyList();
}
+ final List<X509Certificate> result = new ArrayList<>();
+ // If this is a certificate entry we return the single certificate. Otherwise we trim the
+ // leaf and return only the rest of the chain.
+ for (int i = isCertificateEntry ? 0 : 1; i < certs.length; ++i) {
+ if (certs[i] instanceof X509Certificate) {
+ result.add((X509Certificate) certs[i]);
+ } else {
+ Log.w(TAG,"A certificate in the chain of alias \""
+ + alias + "\" is not X509.");
+ return Collections.emptyList();
+ }
+ }
+ return result;
}
}
diff --git a/src/com/android/keychain/KeyChainService.java b/src/com/android/keychain/KeyChainService.java
index 6c03fa1..6686542 100644
--- a/src/com/android/keychain/KeyChainService.java
+++ b/src/com/android/keychain/KeyChainService.java
@@ -18,58 +18,82 @@
import static android.app.admin.SecurityLog.TAG_CERT_AUTHORITY_INSTALLED;
import static android.app.admin.SecurityLog.TAG_CERT_AUTHORITY_REMOVED;
+import static android.security.KeyStore.UID_SELF;
+import android.Manifest;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.app.IntentService;
import android.app.admin.SecurityLog;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.StringParceledListSlice;
+import android.hardware.security.keymint.ErrorCode;
+import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.Process;
import android.os.UserHandle;
-import android.security.Credentials;
+import android.security.AppUriAuthenticationPolicy;
+import android.security.CredentialManagementApp;
import android.security.IKeyChainService;
import android.security.KeyChain;
-import android.security.KeyStore;
-import android.security.keymaster.KeymasterArguments;
-import android.security.keymaster.KeymasterCertificateChain;
-import android.security.keystore.AttestationUtils;
-import android.security.keystore.DeviceIdAttestationException;
+import android.security.KeyStore2;
import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
import android.security.keystore.ParcelableKeyGenParameterSpec;
import android.security.keystore.StrongBoxUnavailableException;
+import android.security.keystore2.AndroidKeyStoreLoadStoreParameter;
+import android.system.keystore2.Domain;
+import android.system.keystore2.KeyDescriptor;
+import android.system.keystore2.KeyPermission;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
import com.android.keychain.internal.ExistingKeysProvider;
import com.android.keychain.internal.GrantsDatabase;
import com.android.org.conscrypt.TrustedCertificateStore;
import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
+import java.security.Key;
+import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.ProviderException;
+import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
+import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import javax.security.auth.x500.X500Principal;
@@ -79,21 +103,68 @@
private static final String TAG = "KeyChain";
private static final String CERT_INSTALLER_PACKAGE = "com.android.certinstaller";
private final Set<Integer> ALLOWED_UIDS = Collections.unmodifiableSet(
- new HashSet(Arrays.asList(KeyStore.UID_SELF, Process.WIFI_UID)));
+ new HashSet(Arrays.asList(UID_SELF, Process.WIFI_UID)));
+
+ private static final String MSG_NOT_SYSTEM = "Not system package";
+ private static final String MSG_NOT_SYSTEM_OR_CERT_INSTALLER =
+ "Not system or cert installer package";
+ private static final String MSG_NOT_SYSTEM_OR_CRED_MNG_APP =
+ "Not system or credential management app package";
/** created in onCreate(), closed in onDestroy() */
private GrantsDatabase mGrantsDb;
private Injector mInjector;
- private final KeyStore mKeyStore = KeyStore.getInstance();
+ private KeyStore mKeyStore;
+ private KeyChainStateStorage mStateStorage;
+
+ private Object mCredentialManagementAppLock = new Object();
+ @Nullable
+ @GuardedBy("mCredentialManagementAppLock")
+ private CredentialManagementApp mCredentialManagementApp;
public KeyChainService() {
super(KeyChainService.class.getSimpleName());
mInjector = new Injector();
}
+ private KeyStore getKeyStore() {
+ try {
+ final KeyStore keystore = mInjector.getKeyStoreInstance();
+ keystore.load(null);
+ return keystore;
+ } catch (KeyStoreException | IOException | NoSuchAlgorithmException
+ | CertificateException e) {
+ Log.e(TAG, "Error opening AndroidKeyStore.", e);
+ throw new RuntimeException("Error opening AndroidKeyStore.", e);
+ }
+ }
+
+ private KeyStore getKeyStore(boolean useWifiNamespace) {
+ if (!useWifiNamespace) {
+ return mKeyStore;
+ }
+ try {
+ final KeyStore keystore = mInjector.getKeyStoreInstance();
+ keystore.load(
+ new AndroidKeyStoreLoadStoreParameter(
+ KeyProperties.NAMESPACE_WIFI));
+ return keystore;
+ } catch (IOException | CertificateException | KeyStoreException
+ | NoSuchAlgorithmException e) {
+ Log.e(TAG, "Failed to open AndroidKeyStore for WI-FI namespace.", e);
+ return null;
+ }
+ }
+
@Override public void onCreate() {
super.onCreate();
+ mKeyStore = getKeyStore();
mGrantsDb = new GrantsDatabase(this, new KeyStoreAliasesProvider(mKeyStore));
+ mStateStorage = new KeyChainStateStorage(getDataDir());
+
+ synchronized (mCredentialManagementAppLock) {
+ mCredentialManagementApp = mStateStorage.loadCredentialManagementApp();
+ }
}
@Override
@@ -112,23 +183,35 @@
@Override
public List<String> getExistingKeyAliases() {
- List<String> aliases = new ArrayList<String>();
- String[] keyStoreAliases = mKeyStore.list(Credentials.USER_PRIVATE_KEY);
- if (keyStoreAliases == null) {
- return aliases;
+ final List<String> keyStoreAliases = new ArrayList<>();
+ try {
+ final Enumeration<String> aliases = mKeyStore.aliases();
+ while (aliases.hasMoreElements()) {
+ final String alias = aliases.nextElement();
+ if (!alias.startsWith(LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX)) {
+ if (mKeyStore.isKeyEntry(alias)) {
+ keyStoreAliases.add(alias);
+ }
+ }
+ }
+ } catch (KeyStoreException e) {
+ Log.e(TAG, "Error while loading entries from keystore. "
+ + "List may be empty or incomplete.");
}
- for (String alias: keyStoreAliases) {
- Log.w(TAG, "Got Alias from KeyStore: " + alias);
- String unPrefixedAlias = alias.replaceFirst("^" + Credentials.USER_PRIVATE_KEY, "");
- if (!unPrefixedAlias.startsWith(LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX)) {
- aliases.add(unPrefixedAlias);
- }
- }
- return aliases;
+ return keyStoreAliases;
}
}
+ private KeyDescriptor makeKeyDescriptor(String alias) {
+ final KeyDescriptor key = new KeyDescriptor();
+ key.domain = Domain.APP;
+ key.nspace = KeyProperties.NAMESPACE_APPLICATION;
+ key.alias = alias;
+ key.blob = null;
+ return key;
+ }
+
private final IKeyChainService.Stub mIKeyChainService = new IKeyChainService.Stub() {
private final TrustedCertificateStore mTrustedCertificateStore
= new TrustedCertificateStore();
@@ -136,28 +219,83 @@
@Override
public String requestPrivateKey(String alias) {
- if (!hasGrant(alias)) {
+ final CallerIdentity caller = getCaller();
+ if (!hasGrant(alias, caller)) {
return null;
}
- final String keystoreAlias = Credentials.USER_PRIVATE_KEY + alias;
- final int uid = mInjector.getCallingUid();
- Log.i(TAG, String.format("UID %d will be granted access to %s", uid, keystoreAlias));
- return mKeyStore.grant(keystoreAlias, uid);
+ final int granteeUid = caller.mUid;
+
+ try {
+ final KeyStore2 keyStore2 = KeyStore2.getInstance();
+ KeyDescriptor grant = keyStore2.grant(makeKeyDescriptor(alias), granteeUid,
+ KeyPermission.USE | KeyPermission.GET_INFO);
+ return KeyChain.getGrantString(grant);
+ } catch (android.security.KeyStoreException e) {
+ Log.e(TAG, "Failed to grant " + alias + " to uid: " + granteeUid, e);
+ return null;
+ }
+ }
+
+ @Override
+ public String getWifiKeyGrantAsUser(String alias) {
+ Preconditions.checkCallAuthorization(isSystemUid(getCaller()), MSG_NOT_SYSTEM);
+
+ if (!hasGrant(alias, Process.WIFI_UID)) {
+ return null;
+ }
+
+ KeyStore2 keyStore2 = KeyStore2.getInstance();
+ try {
+ KeyDescriptor grant = keyStore2.grant(makeKeyDescriptor(alias),
+ Process.WIFI_UID, KeyPermission.USE | KeyPermission.GET_INFO);
+ return KeyStore2.makeKeystoreEngineGrantString(grant.nspace);
+ } catch (android.security.KeyStoreException e) {
+ Log.e(TAG, "Failed to grant " + alias + " to uid: " + Process.WIFI_UID, e);
+ return null;
+ }
}
@Override public byte[] getCertificate(String alias) {
- if (!hasGrant(alias)) {
+ final CallerIdentity caller = getCaller();
+ if (!hasGrant(alias, caller) && !isSystemUid(caller)) {
return null;
}
- return mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
+ try {
+ if (!mKeyStore.isCertificateEntry(alias)) {
+ final Certificate cert = mKeyStore.getCertificate(alias);
+ if (cert == null) return null;
+ return cert.getEncoded();
+ } else {
+ return null;
+ }
+ } catch (KeyStoreException | CertificateEncodingException e) {
+ Log.e(TAG, "Failed to retrieve certificate.", e);
+ return null;
+ }
}
@Override public byte[] getCaCertificates(String alias) {
- if (!hasGrant(alias)) {
+ final CallerIdentity caller = getCaller();
+ if (!hasGrant(alias, caller) && !isSystemUid(caller)) {
return null;
}
- return mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
+ try {
+ if (mKeyStore.isCertificateEntry(alias)) {
+ return mKeyStore.getCertificate(alias).getEncoded();
+ } else {
+ final Certificate[] certs = mKeyStore.getCertificateChain(alias);
+ if (certs == null || certs.length <= 1) return null;
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ for (int i = 1; i < certs.length; ++i) {
+ outputStream.write(certs[i].getEncoded());
+ }
+ return outputStream.toByteArray();
+ }
+ } catch (KeyStoreException | CertificateEncodingException | IOException e) {
+ Log.e(TAG, "Failed to retrieve certificate(s) from AndroidKeyStore.", e);
+ return null;
+ }
}
@Override public boolean isUserSelectable(String alias) {
@@ -167,7 +305,7 @@
@Override public void setUserSelectable(String alias, boolean isUserSelectable) {
validateAlias(alias);
- checkSystemCaller();
+ Preconditions.checkCallAuthorization(isSystemUid(getCaller()), MSG_NOT_SYSTEM);
Log.i(TAG, String.format("Marking certificate %s as user-selectable: %b", alias,
isUserSelectable));
mGrantsDb.setIsUserSelectable(alias, isUserSelectable);
@@ -175,7 +313,7 @@
@Override public int generateKeyPair(
String algorithm, ParcelableKeyGenParameterSpec parcelableSpec) {
- checkSystemCaller();
+ Preconditions.checkCallAuthorization(isSystemUid(getCaller()), MSG_NOT_SYSTEM);
final KeyGenParameterSpec spec = parcelableSpec.getSpec();
final String alias = spec.getKeystoreAlias();
@@ -188,23 +326,11 @@
}
// Validate the alias here to avoid relying on KeyGenParameterSpec c'tor preventing
// the creation of a KeyGenParameterSpec instance with a non-empty alias.
- if (TextUtils.isEmpty(alias) || spec.getUid() != KeyStore.UID_SELF) {
+ if (TextUtils.isEmpty(alias) || spec.getUid() != UID_SELF) {
Log.e(TAG, "Cannot generate key pair with empty alias or specified uid.");
return KeyChain.KEY_GEN_MISSING_ALIAS;
}
- if (spec.getAttestationChallenge() != null) {
- Log.e(TAG, "Key generation request should not include an Attestation challenge.");
- return KeyChain.KEY_GEN_SUPERFLUOUS_ATTESTATION_CHALLENGE;
- }
-
- if (!removeKeyPair(alias)) {
- Log.e(TAG, "Failed to remove previously-installed alias " + alias);
- //TODO: Introduce a different error code in R to distinguish the failure to remove
- // old keys from other failures.
- return KeyChain.KEY_GEN_FAILURE;
- }
-
try {
KeyPairGenerator generator = KeyPairGenerator.getInstance(
algorithm, "AndroidKeyStore");
@@ -230,98 +356,58 @@
} catch (StrongBoxUnavailableException e) {
Log.e(TAG, "StrongBox unavailable.", e);
return KeyChain.KEY_GEN_STRONGBOX_UNAVAILABLE;
- }
- }
-
- @Override public int attestKey(
- String alias, byte[] attestationChallenge,
- int[] idAttestationFlags,
- KeymasterCertificateChain attestationChain) {
- checkSystemCaller();
- validateAlias(alias);
-
- if (attestationChallenge == null) {
- Log.e(TAG, String.format("Missing attestation challenge for alias %s", alias));
- return KeyChain.KEY_ATTESTATION_MISSING_CHALLENGE;
- }
-
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, String.format("About to attest key alias %s, challenge %s, flags %s",
- alias, Base64.encodeToString(attestationChallenge, Base64.NO_WRAP),
- Arrays.toString(idAttestationFlags)));
- }
-
- KeymasterArguments attestArgs;
- try {
- attestArgs = AttestationUtils.prepareAttestationArguments(
- mContext, idAttestationFlags, attestationChallenge);
- } catch (DeviceIdAttestationException e) {
- Log.e(TAG, "Failed collecting attestation data", e);
- return KeyChain.KEY_ATTESTATION_CANNOT_COLLECT_DATA;
- }
- int errorCode = checkKeyChainStatus(alias, attestationChain, attestArgs);
- if (errorCode == KeyChain.KEY_ATTESTATION_CANNOT_ATTEST_IDS) {
- // b/69471841: id attestation might fail due to incorrect provisioning of device
- try {
- attestArgs =
- AttestationUtils.prepareAttestationArgumentsIfMisprovisioned(
- mContext, idAttestationFlags, attestationChallenge);
- if (attestArgs == null) {
- return errorCode;
+ } catch (ProviderException e) {
+ Throwable t = e.getCause();
+ if (t instanceof android.security.KeyStoreException) {
+ if (((android.security.KeyStoreException) t).getErrorCode()
+ == ErrorCode.CANNOT_ATTEST_IDS) {
+ return KeyChain.KEY_ATTESTATION_CANNOT_ATTEST_IDS;
}
- } catch (DeviceIdAttestationException e) {
- Log.e(TAG, "Failed collecting attestation data "
- + "during second attempt on misprovisioned device", e);
- return KeyChain.KEY_ATTESTATION_CANNOT_COLLECT_DATA;
}
+ Log.e(TAG, "KeyStore error.", e);
+ return KeyChain.KEY_GEN_FAILURE;
}
-
- return checkKeyChainStatus(alias, attestationChain, attestArgs);
- }
-
- private int checkKeyChainStatus(
- String alias,
- KeymasterCertificateChain attestationChain,
- KeymasterArguments attestArgs) {
-
- final String keystoreAlias = Credentials.USER_PRIVATE_KEY + alias;
- final int errorCode = mKeyStore.attestKey(keystoreAlias, attestArgs, attestationChain);
- if (errorCode != KeyStore.NO_ERROR) {
- Log.e(TAG, String.format("Failure attesting for key %s: %d", alias, errorCode));
- if (errorCode == KeyStore.CANNOT_ATTEST_IDS) {
- return KeyChain.KEY_ATTESTATION_CANNOT_ATTEST_IDS;
- } else {
- // General failure, cannot discern which.
- return KeyChain.KEY_ATTESTATION_FAILURE;
- }
- }
-
- return KeyChain.KEY_ATTESTATION_SUCCESS;
}
@Override public boolean setKeyPairCertificate(String alias, byte[] userCertificate,
byte[] userCertificateChain) {
- checkSystemCaller();
- if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, userCertificate,
- KeyStore.UID_SELF, KeyStore.FLAG_NONE)) {
- Log.e(TAG, "Failed to import user certificate " + userCertificate);
+ Preconditions.checkCallAuthorization(isSystemUid(getCaller()), MSG_NOT_SYSTEM);
+
+ final PrivateKey privateKey;
+ try {
+ final Key key = mKeyStore.getKey(alias, null);
+ if (! (key instanceof PrivateKey)) {
+ return false;
+ }
+ privateKey = (PrivateKey) key;
+ } catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException e) {
+ Log.e(TAG, "Failed to get private key entry.", e);
return false;
}
- if (userCertificateChain != null && userCertificateChain.length > 0) {
- if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, userCertificateChain,
- KeyStore.UID_SELF, KeyStore.FLAG_NONE)) {
- Log.e(TAG, "Failed to import certificate chain" + userCertificateChain);
- if (!mKeyStore.delete(Credentials.USER_CERTIFICATE + alias)) {
- Log.e(TAG, "Failed to clean up key chain after certificate chain"
- + " importing failed");
- }
- return false;
+ final ArrayList<Certificate> certs = new ArrayList<>();
+ try {
+ if (userCertificate != null) {
+ certs.add(parseCertificate(userCertificate));
}
- } else {
- if (!mKeyStore.delete(Credentials.CA_CERTIFICATE + alias)) {
- Log.e(TAG, "Failed to remove CA certificate chain for alias " + alias);
+ if (userCertificateChain != null) {
+ certs.addAll(parseCertificates(userCertificateChain));
}
+ } catch (CertificateException e) {
+ Log.e(TAG, "Failed to parse user certificate.", e);
+ return false;
+ }
+
+ final Certificate[] certsArray = certs.toArray(new Certificate[0]);
+
+ try {
+ // setKeyEntry with a private key loaded from AndroidKeyStore replaces
+ // the certificate components without touching the private key if
+ // the alias is the same as that of the private key.
+ mKeyStore.setKeyEntry(alias, privateKey, null, certsArray);
+ } catch (KeyStoreException e) {
+ Log.e(TAG, "Failed update key certificates.", e);
+ return false;
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
@@ -340,14 +426,17 @@
}
}
- private boolean hasGrant(String alias) {
+ private boolean hasGrant(String alias, CallerIdentity caller) {
+ return hasGrant(alias, caller.mUid);
+ }
+
+ private boolean hasGrant(String alias, int targetUid) {
validateAlias(alias);
- final int callingUid = mInjector.getCallingUid();
- if (!mGrantsDb.hasGrant(callingUid, alias)) {
+ if (!mGrantsDb.hasGrant(targetUid, alias)) {
Log.w(TAG, String.format(
"uid %d doesn't have permission to access the requested alias %s",
- callingUid, alias));
+ targetUid, alias));
return false;
}
@@ -355,12 +444,15 @@
}
@Override public String installCaCertificate(byte[] caCertificate) {
- checkCertInstallerOrSystemCaller();
+ final CallerIdentity caller = getCaller();
+ Preconditions.checkCallAuthorization(isSystemUid(caller) || isCertInstaller(caller),
+ MSG_NOT_SYSTEM_OR_CERT_INSTALLER);
final String alias;
String subject = null;
final boolean isSecurityLoggingEnabled = mInjector.isSecurityLoggingEnabled();
+ final X509Certificate cert;
try {
- final X509Certificate cert = parseCertificate(caCertificate);
+ cert = parseCertificate(caCertificate);
final boolean isDebugLoggable = Log.isLoggable(TAG, Log.DEBUG);
subject = cert.getSubjectX500Principal().getName(X500Principal.CANONICAL);
@@ -392,14 +484,14 @@
// anchors. Ultimately, the user should explicitly choose to install the VPN trust
// anchor separately and independently of CA certificates, at which point this code
// should be removed.
- if (CERT_INSTALLER_PACKAGE.equals(callingPackage())) {
- final boolean result = mKeyStore.put(
- String.format("%s%s %s", Credentials.CA_CERTIFICATE, subject, alias),
- caCertificate, Process.SYSTEM_UID,
- KeyStore.FLAG_NONE);
- Log.d(TAG, String.format(
- "Attempted installing %s (subject: %s) to KeyStore. Result: %b", alias,
- subject, result));
+ if (CERT_INSTALLER_PACKAGE.equals(caller.mPackageName)) {
+ try {
+ mKeyStore.setCertificateEntry(String.format("%s %s", subject, alias), cert);
+ } catch(KeyStoreException e) {
+ Log.e(TAG, String.format(
+ "Attempted installing %s (subject: %s) to KeyStore. Failed", alias,
+ subject), e);
+ }
}
broadcastLegacyStorageChange();
@@ -415,17 +507,19 @@
* @param userCertificateChain The rest of the chain for the client certificate
* @param alias The alias under which the key pair is installed. It is invalid to pass
* {@code KeyChain.KEY_ALIAS_SELECTION_DENIED}.
- * @param uid Can be only one of two values: Either {@code KeyStore.UID_SELF} to indicate
- * installation into the current user's system Keystore instance, or
- * {@code Process.WIFI_UID} to indicate installation into the main user's
- * WiFi Keystore instance. It is only valid to pass {@code Process.WIFI_UID} to
- * the KeyChain service on user 0.
+ * @param uid Can be only one of two values: Either
+ * {@code android.security.KeyStore.UID_SELF} to indicate installation into the
+ * current user's system Keystore instance, or {@code Process.WIFI_UID} to
+ * indicate installation into the main user's WiFi Keystore instance. It is only
+ * valid to pass {@code Process.WIFI_UID} to the KeyChain service on user 0.
* @return Whether the operation succeeded or not.
*/
@Override public boolean installKeyPair(@Nullable byte[] privateKey,
@Nullable byte[] userCertificate, @Nullable byte[] userCertificateChain,
String alias, int uid) {
- checkCertInstallerOrSystemCaller();
+ final CallerIdentity caller = getCaller();
+ Preconditions.checkCallAuthorization(isSystemUid(caller) || isCertInstaller(caller),
+ MSG_NOT_SYSTEM_OR_CERT_INSTALLER);
if (KeyChain.KEY_ALIAS_SELECTION_DENIED.equals(alias)) {
throw new IllegalArgumentException("The alias specified for the key denotes "
+ "a reserved value and cannot be used to name a key");
@@ -456,42 +550,78 @@
emptyOrBase64Encoded(userCertificateChain)));
}
- if (!removeKeyPair(alias)) {
- return false;
- }
- if (privateKey != null && !mKeyStore.importKey(
- Credentials.USER_PRIVATE_KEY + alias, privateKey, uid, KeyStore.FLAG_NONE)) {
- Log.e(TAG, "Failed to import private key " + alias);
- return false;
- }
- if (userCertificate != null &&
- !mKeyStore.put(Credentials.USER_CERTIFICATE + alias, userCertificate,
- uid, KeyStore.FLAG_NONE)) {
- Log.e(TAG, "Failed to import user certificate " + userCertificate);
- if (!mKeyStore.delete(Credentials.USER_PRIVATE_KEY + alias)) {
- Log.e(TAG, "Failed to delete private key after certificate importing failed");
+ final ArrayList<Certificate> certs = new ArrayList<>();
+ try {
+ if (userCertificate != null) {
+ certs.add(parseCertificate(userCertificate));
}
+ if (userCertificateChain != null) {
+ certs.addAll(parseCertificates(userCertificateChain));
+ }
+ } catch (CertificateException e) {
+ Log.e(TAG, "Failed to parse user certificate.", e);
return false;
}
- if (userCertificateChain != null && userCertificateChain.length > 0) {
- if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, userCertificateChain, uid,
- KeyStore.FLAG_NONE)) {
- Log.e(TAG, "Failed to import certificate chain" + userCertificateChain);
- if (!removeKeyPair(alias)) {
- Log.e(TAG, "Failed to clean up key chain after certificate chain"
- + " importing failed");
+
+ if (certs.isEmpty()) {
+ Log.e(TAG, "Cannot install private key without public certificate.");
+ return false;
+ }
+
+ final Certificate[] certificates = certs.toArray(new Certificate[0]);
+
+ final PrivateKey privateKey1;
+ try {
+ if (privateKey != null) {
+ final KeyFactory keyFactory =
+ KeyFactory.getInstance(certificates[0].getPublicKey().getAlgorithm());
+ privateKey1 = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKey));
+ } else {
+ privateKey1 = null;
+ }
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ Log.e(TAG, "Failed to parse private key.", e);
+ return false;
+ }
+
+ KeyStore keystore = getKeyStore(uid == Process.WIFI_UID);
+ if (keystore == null) {
+ return false;
+ }
+
+ try {
+ if (privateKey != null) {
+ keystore.setKeyEntry(alias, privateKey1, null, certificates);
+ } else {
+ if (certificates.length > 1) {
+ Log.e(TAG,
+ "Cannot install key certificate chain without private key.");
+ return false;
}
- return false;
+ keystore.setCertificateEntry(alias, certificates[0]);
}
+ } catch (KeyStoreException e) {
+ Log.e(TAG, "Failed to install key pair.", e);
}
+
broadcastKeychainChange();
broadcastLegacyStorageChange();
return true;
}
@Override public boolean removeKeyPair(String alias) {
- checkCertInstallerOrSystemCaller();
- if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias)) {
+ final CallerIdentity caller = getCaller();
+ Preconditions.checkCallAuthorization(isSystemUid(caller) || isCertInstaller(caller),
+ MSG_NOT_SYSTEM_OR_CERT_INSTALLER);
+ return removeKeyPairInternal(alias);
+ }
+
+ private boolean removeKeyPairInternal(String alias) {
+ try {
+ mKeyStore.deleteEntry(alias);
+ } catch (KeyStoreException e) {
+ Log.e(TAG, String.format(
+ "Failed not remove keystore entry with alias %s", alias));
return false;
}
Log.w(TAG, String.format(
@@ -502,14 +632,31 @@
return true;
}
+ @Override public boolean containsKeyPair(String alias) {
+ Preconditions.checkCallAuthorization(isSystemUid(getCaller()), MSG_NOT_SYSTEM);
+ try {
+ final Key key = mKeyStore.getKey(alias, null);
+ return key instanceof PrivateKey;
+ } catch (UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException e) {
+ Log.w("Error while trying to check for key presence.", e);
+ return false;
+ }
+ }
+
private X509Certificate parseCertificate(byte[] bytes) throws CertificateException {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(bytes));
}
+ private Collection<X509Certificate> parseCertificates(byte[] bytes)
+ throws CertificateException {
+ final CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ return (Collection<X509Certificate>)
+ cf.generateCertificates(new ByteArrayInputStream(bytes));
+ }
@Override public boolean reset() {
// only Settings should be able to reset
- checkSystemCaller();
+ Preconditions.checkCallAuthorization(isSystemUid(getCaller()), MSG_NOT_SYSTEM);
mGrantsDb.removeAllAliasesInformation();
boolean ok = true;
synchronized (mTrustedCertificateStore) {
@@ -530,7 +677,7 @@
@Override public boolean deleteCaCertificate(String alias) {
// only Settings should be able to delete
- checkSystemCaller();
+ Preconditions.checkCallAuthorization(isSystemUid(getCaller()), MSG_NOT_SYSTEM);
boolean ok = true;
Log.i(TAG, String.format("Deleting CA certificate %s", alias));
synchronized (mTrustedCertificateStore) {
@@ -570,37 +717,58 @@
}
}
- private void checkCertInstallerOrSystemCaller() {
- final String caller = callingPackage();
- if (!isCallerWithSystemUid() && !CERT_INSTALLER_PACKAGE.equals(caller)) {
- throw new SecurityException("Not system or cert installer package: " + caller);
+ private boolean hasManageCredentialManagementAppPermission(CallerIdentity caller) {
+ return mContext.checkPermission(Manifest.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP,
+ caller.mPid, caller.mUid) == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private boolean isCertInstaller(CallerIdentity caller) {
+ return caller.mPackageName != null
+ && CERT_INSTALLER_PACKAGE.equals(caller.mPackageName);
+ }
+
+ private boolean isCredentialManagementApp(CallerIdentity caller) {
+ synchronized (mCredentialManagementAppLock) {
+ return mCredentialManagementApp != null && caller.mPackageName != null
+ && caller.mPackageName.equals(mCredentialManagementApp.getPackageName());
}
}
- private void checkSystemCaller() {
- if (!isCallerWithSystemUid()) {
- throw new SecurityException("Not system package: " + callingPackage());
- }
- }
-
- private boolean isCallerWithSystemUid() {
- return UserHandle.isSameApp(mInjector.getCallingUid(), Process.SYSTEM_UID);
- }
-
- private String callingPackage() {
- return getPackageManager().getNameForUid(mInjector.getCallingUid());
+ private boolean isSystemUid(CallerIdentity caller) {
+ return UserHandle.isSameApp(caller.mUid, Process.SYSTEM_UID);
}
@Override public boolean hasGrant(int uid, String alias) {
- checkSystemCaller();
+ Preconditions.checkCallAuthorization(isSystemUid(getCaller()), MSG_NOT_SYSTEM);
return mGrantsDb.hasGrant(uid, alias);
}
- @Override public void setGrant(int uid, String alias, boolean value) {
- checkSystemCaller();
- mGrantsDb.setGrant(uid, alias, value);
- broadcastPermissionChange(uid, alias, value);
+ @Override public boolean setGrant(int uid, String alias, boolean granted) {
+ Preconditions.checkCallAuthorization(isSystemUid(getCaller()), MSG_NOT_SYSTEM);
+ mGrantsDb.setGrant(uid, alias, granted);
+ if (!granted) {
+ try {
+ KeyStore2.getInstance().ungrant(makeKeyDescriptor(alias), uid);
+ } catch (android.security.KeyStoreException e) {
+ Log.e(TAG, "Failed to ungrant " + alias + " to uid: " + uid, e);
+ return false;
+ }
+ }
+ broadcastPermissionChange(uid, alias, granted);
broadcastLegacyStorageChange();
+ return true;
+ }
+
+ @Override public int[] getGrants(String alias) {
+ Preconditions.checkCallAuthorization(isSystemUid(getCaller()), MSG_NOT_SYSTEM);
+ try {
+ if (mKeyStore.isKeyEntry(alias)) {
+ return mGrantsDb.getGrants(alias);
+ }
+ } catch (KeyStoreException e) {
+ Log.w(TAG, "Error while checking if key exists.", e);
+ }
+ throw new IllegalArgumentException("Alias not found: " + alias);
}
@Override
@@ -667,6 +835,149 @@
}
}
}
+
+ @Override
+ public void setCredentialManagementApp(@NonNull String packageName,
+ @NonNull AppUriAuthenticationPolicy authenticationPolicy) {
+ final CallerIdentity caller = getCaller();
+ Preconditions.checkCallAuthorization(isSystemUid(caller)
+ || hasManageCredentialManagementAppPermission(caller), MSG_NOT_SYSTEM);
+ checkValidAuthenticationPolicy(authenticationPolicy);
+
+ synchronized (mCredentialManagementAppLock) {
+ if (mCredentialManagementApp != null) {
+ final String existingPackage = mCredentialManagementApp.getPackageName();
+ if (existingPackage.equals(packageName)) {
+ // Update existing credential management app's policy
+ removeOrphanedKeyPairs(authenticationPolicy);
+ } else {
+ // Replace existing credential management app
+ removeOrphanedKeyPairs(null);
+ setManageCredentialsAppOps(existingPackage, false);
+ }
+ }
+ setManageCredentialsAppOps(packageName, true);
+ mCredentialManagementApp = new CredentialManagementApp(packageName,
+ authenticationPolicy);
+ mStateStorage.saveCredentialManagementApp(mCredentialManagementApp);
+ }
+ }
+
+ private void setManageCredentialsAppOps(String packageName, boolean allowed) {
+ try {
+ int mode = allowed ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_DEFAULT;
+ ApplicationInfo appInfo = getPackageManager().getApplicationInfo(packageName, 0);
+ getSystemService(AppOpsManager.class).setMode(AppOpsManager.OP_MANAGE_CREDENTIALS,
+ appInfo.uid, packageName, mode);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Unable to find info for package: " + packageName);
+ }
+ }
+
+ private void removeOrphanedKeyPairs(
+ @Nullable AppUriAuthenticationPolicy newPolicy) {
+ Set<String> existingAliases = mCredentialManagementApp.getAuthenticationPolicy()
+ .getAliases();
+ Set<String> newAliases = newPolicy != null ? newPolicy.getAliases() : new HashSet<>();
+
+ // Uninstall all certificates that are no longer included in the new
+ // authentication policy
+ for (String existingAlias : existingAliases) {
+ if (!newAliases.contains(existingAlias)) {
+ removeKeyPairInternal(existingAlias);
+ }
+ }
+ }
+
+ private void checkValidAuthenticationPolicy(
+ @NonNull AppUriAuthenticationPolicy authenticationPolicy) {
+ if (authenticationPolicy == null
+ || authenticationPolicy.getAppAndUriMappings().isEmpty()) {
+ throw new IllegalArgumentException("The authentication policy is null or empty");
+ }
+ // Check whether any of the aliases in the policy already exist
+ for (String alias : authenticationPolicy.getAliases()) {
+ if (requestPrivateKey(alias) != null) {
+ throw new IllegalArgumentException(String.format("The authentication policy "
+ + "contains an installed alias: %s", alias));
+ }
+ }
+ }
+
+ @Override
+ public boolean hasCredentialManagementApp() {
+ Preconditions.checkCallAuthorization(isSystemUid(getCaller()), MSG_NOT_SYSTEM);
+ synchronized (mCredentialManagementAppLock) {
+ return mCredentialManagementApp != null;
+ }
+ }
+
+ @Nullable
+ @Override
+ public String getCredentialManagementAppPackageName() {
+ Preconditions.checkCallAuthorization(isSystemUid(getCaller()), MSG_NOT_SYSTEM);
+ synchronized (mCredentialManagementAppLock) {
+ return mCredentialManagementApp != null
+ ? mCredentialManagementApp.getPackageName()
+ : null;
+ }
+ }
+
+ @Nullable
+ @Override
+ public AppUriAuthenticationPolicy getCredentialManagementAppPolicy() {
+ final CallerIdentity caller = getCaller();
+ Preconditions.checkCallAuthorization(isSystemUid(caller)
+ || isCredentialManagementApp(caller), MSG_NOT_SYSTEM_OR_CRED_MNG_APP);
+ synchronized (mCredentialManagementAppLock) {
+ return mCredentialManagementApp != null
+ ? mCredentialManagementApp.getAuthenticationPolicy()
+ : null;
+ }
+ }
+
+ @Nullable
+ @Override
+ public String getPredefinedAliasForPackageAndUri(@NonNull String packageName,
+ @Nullable Uri uri) {
+ Preconditions.checkCallAuthorization(isSystemUid(getCaller()), MSG_NOT_SYSTEM);
+ synchronized (mCredentialManagementAppLock) {
+ if (mCredentialManagementApp == null || uri == null) {
+ return null;
+ }
+ Map<Uri, String> urisToAliases = mCredentialManagementApp.getAuthenticationPolicy()
+ .getAppAndUriMappings().get(packageName);
+ return urisToAliases != null ? urisToAliases.get(uri) : null;
+ }
+ }
+
+ @Override
+ public void removeCredentialManagementApp() {
+ final CallerIdentity caller = getCaller();
+ Preconditions.checkCallAuthorization(isSystemUid(caller)
+ || isCredentialManagementApp(caller)
+ || hasManageCredentialManagementAppPermission(caller),
+ MSG_NOT_SYSTEM_OR_CRED_MNG_APP);
+ synchronized (mCredentialManagementAppLock) {
+ if (mCredentialManagementApp != null) {
+ // Remove all certificates
+ removeOrphanedKeyPairs(null);
+ setManageCredentialsAppOps(mCredentialManagementApp.getPackageName(), false);
+ }
+ mCredentialManagementApp = null;
+ mStateStorage.saveCredentialManagementApp(mCredentialManagementApp);
+ }
+ }
+
+ @Override
+ public boolean isCredentialManagementApp(@NonNull String packageName) {
+ final CallerIdentity caller = getCaller();
+ Preconditions.checkCallAuthorization(isSystemUid(caller)
+ || isCredentialManagementApp(caller), MSG_NOT_SYSTEM_OR_CRED_MNG_APP);
+ synchronized (mCredentialManagementAppLock) {
+ return packageName.equals(mCredentialManagementApp.getPackageName());
+ }
+ }
};
@Override public IBinder onBind(Intent intent) {
@@ -723,6 +1034,23 @@
return Base64.encodeToString(cert, Base64.NO_WRAP);
}
+ private final class CallerIdentity {
+
+ final int mUid;
+ final int mPid;
+ final String mPackageName;
+
+ CallerIdentity() {
+ mUid = mInjector.getCallingUid();
+ mPid = Binder.getCallingPid();
+ mPackageName = getPackageManager().getNameForUid(mUid);
+ }
+ }
+
+ private CallerIdentity getCaller() {
+ return new CallerIdentity();
+ }
+
@VisibleForTesting
void setInjector(Injector injector) {
mInjector = injector;
@@ -744,5 +1072,9 @@
public int getCallingUid() {
return Binder.getCallingUid();
}
+
+ public KeyStore getKeyStoreInstance() throws KeyStoreException {
+ return KeyStore.getInstance("AndroidKeyStore");
+ }
}
}
diff --git a/src/com/android/keychain/KeyChainStateStorage.java b/src/com/android/keychain/KeyChainStateStorage.java
new file mode 100644
index 0000000..5a791c1
--- /dev/null
+++ b/src/com/android/keychain/KeyChainStateStorage.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keychain;
+
+import android.annotation.Nullable;
+import android.security.CredentialManagementApp;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+public class KeyChainStateStorage {
+
+ private static final String TAG = "KeyChain";
+ private static final String TAG_CREDENTIAL_MANAGEMENT_APP = "credential-management-app";
+
+ private final File mDirectory;
+
+ public KeyChainStateStorage(File directory) {
+ mDirectory = directory;
+ }
+
+ @Nullable
+ public CredentialManagementApp loadCredentialManagementApp() {
+ CredentialManagementApp credentialManagementApp = null;
+ AtomicFile file = getCredentialManagementFile();
+ FileInputStream stream = null;
+ try {
+ stream = file.openRead();
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, StandardCharsets.UTF_8.name());
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ }
+ String tag = parser.getName();
+ if (TAG_CREDENTIAL_MANAGEMENT_APP.equals(tag)) {
+ credentialManagementApp = CredentialManagementApp.readFromXml(parser);
+ }
+ } catch (XmlPullParserException | IOException e) {
+ Log.e(TAG, "Failed to load state", e);
+ } finally {
+ try {
+ if (stream != null) {
+ stream.close();
+ }
+ } catch (IOException e) {
+ }
+ }
+ return credentialManagementApp;
+ }
+
+ public void saveCredentialManagementApp(
+ @Nullable CredentialManagementApp credentialManagementApp) {
+ AtomicFile file = getCredentialManagementFile();
+ FileOutputStream stream;
+ try {
+ stream = file.startWrite();
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to write state " + e);
+ return;
+ }
+ try {
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(stream, StandardCharsets.UTF_8.name());
+ out.startDocument(null, true);
+
+ if (credentialManagementApp != null) {
+ out.startTag(null, TAG_CREDENTIAL_MANAGEMENT_APP);
+ credentialManagementApp.writeToXml(out);
+ out.endTag(null, TAG_CREDENTIAL_MANAGEMENT_APP);
+ }
+ out.endDocument();
+ file.finishWrite(stream);
+ stream.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to store state");
+ file.failWrite(stream);
+ }
+ }
+
+ private AtomicFile getCredentialManagementFile() {
+ File file = new File(mDirectory, "credential-management-app.xml");
+ if (!file.exists()) {
+ try {
+ file.createNewFile();
+ } catch (IOException e) {
+ }
+ }
+ return new AtomicFile(file);
+ }
+}
diff --git a/src/com/android/keychain/internal/GrantsDatabase.java b/src/com/android/keychain/internal/GrantsDatabase.java
index 7c14a44..789ce6f 100644
--- a/src/com/android/keychain/internal/GrantsDatabase.java
+++ b/src/com/android/keychain/internal/GrantsDatabase.java
@@ -25,6 +25,9 @@
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
+import java.util.ArrayList;
+import java.util.List;
+
public class GrantsDatabase {
private static final String TAG = "KeyChain";
@@ -43,6 +46,13 @@
+ GRANTS_ALIAS
+ "=?";
+ private static final String SELECTION_GRANTEE_UIDS_FOR_ALIAS =
+ "SELECT " + GRANTS_GRANTEE_UID + " FROM "
+ + TABLE_GRANTS
+ + " WHERE "
+ + GRANTS_ALIAS
+ + "=?";
+
private static final String SELECT_GRANTS_BY_UID_AND_ALIAS =
GRANTS_GRANTEE_UID + "=? AND " + GRANTS_ALIAS + "=?";
@@ -185,6 +195,25 @@
}
}
+ public int[] getGrants(String alias) {
+ final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
+ try (Cursor cursor =
+ db.query(
+ TABLE_GRANTS,
+ new String[] {GRANTS_GRANTEE_UID},
+ SELECTION_GRANTS_BY_ALIAS,
+ new String[] {alias},
+ null /* group by */,
+ null /* having */,
+ null /* order by */)) {
+ final List<Integer> result = new ArrayList<>();
+ while (cursor.moveToNext()) {
+ result.add(cursor.getInt(0));
+ }
+ return result.stream().mapToInt(Integer::intValue).toArray();
+ }
+ }
+
public void removeAliasInformation(String alias) {
final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
db.delete(TABLE_GRANTS, SELECTION_GRANTS_BY_ALIAS, new String[] {alias});
diff --git a/support/Android.bp b/support/Android.bp
index 426799c..3859d9b 100644
--- a/support/Android.bp
+++ b/support/Android.bp
@@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
java_library {
name: "com.android.keychain.tests.support",
srcs: ["src/com/android/keychain/tests/support/IKeyChainServiceTestSupport.aidl"],
@@ -26,7 +30,4 @@
"junit",
],
certificate: "platform",
- test_suites: [
- "general-tests",
- ]
}
diff --git a/support/AndroidManifest.xml b/support/AndroidManifest.xml
index 934660a..11e7f55 100644
--- a/support/AndroidManifest.xml
+++ b/support/AndroidManifest.xml
@@ -18,7 +18,8 @@
package="com.android.keychain.tests.support"
android:sharedUserId="android.uid.system">
<application android:process="system">
- <service android:name="com.android.keychain.tests.support.KeyChainServiceTestSupport">
+ <service android:name="com.android.keychain.tests.support.KeyChainServiceTestSupport"
+ android:exported="true">
<intent-filter>
<action android:name="com.android.keychain.tests.support.IKeyChainServiceTestSupport"/>
</intent-filter>
diff --git a/support/src/com/android/keychain/tests/support/IKeyChainServiceTestSupport.aidl b/support/src/com/android/keychain/tests/support/IKeyChainServiceTestSupport.aidl
index c62c971..c8ecc76 100644
--- a/support/src/com/android/keychain/tests/support/IKeyChainServiceTestSupport.aidl
+++ b/support/src/com/android/keychain/tests/support/IKeyChainServiceTestSupport.aidl
@@ -31,16 +31,11 @@
* @hide
*/
interface IKeyChainServiceTestSupport {
- boolean keystoreReset();
- boolean keystoreSetPassword(String password);
- boolean keystorePut(String key, in byte[] value);
- boolean keystoreImportKey(String key, in byte[] value);
void revokeAppPermission(int uid, String alias);
void grantAppPermission(int uid, String alias);
boolean installKeyPair(in byte[] privateKey, in byte[] userCert, in byte[] certChain, String alias);
boolean removeKeyPair(String alias);
void setUserSelectable(String alias, boolean isUserSelectable);
int generateKeyPair(in String algorithm, in ParcelableKeyGenParameterSpec spec);
- int attestKey(in String alias, in byte[] challenge, in int[] idAttestationFlags);
boolean setKeyPairCertificate(String alias, in byte[] userCert, in byte[] certChain);
}
diff --git a/support/src/com/android/keychain/tests/support/KeyChainServiceTestSupport.java b/support/src/com/android/keychain/tests/support/KeyChainServiceTestSupport.java
index 5f688f8..1e99770 100644
--- a/support/src/com/android/keychain/tests/support/KeyChainServiceTestSupport.java
+++ b/support/src/com/android/keychain/tests/support/KeyChainServiceTestSupport.java
@@ -23,39 +23,14 @@
import android.security.IKeyChainService;
import android.security.KeyChain;
import android.security.KeyStore;
-import android.security.keymaster.KeymasterCertificateChain;
import android.security.keystore.ParcelableKeyGenParameterSpec;
import android.util.Log;
public class KeyChainServiceTestSupport extends Service {
private static final String TAG = "KeyChainServiceTest";
- private final KeyStore mKeyStore = KeyStore.getInstance();
-
private final IKeyChainServiceTestSupport.Stub mIKeyChainServiceTestSupport
= new IKeyChainServiceTestSupport.Stub() {
- @Override public boolean keystoreReset() {
- Log.d(TAG, "keystoreReset");
- for (String key : mKeyStore.list("")) {
- if (!mKeyStore.delete(key, KeyStore.UID_SELF)) {
- return false;
- }
- }
- return true;
- }
- @Override public boolean keystoreSetPassword(String password) {
- Log.d(TAG, "keystoreSetPassword");
- return mKeyStore.onUserPasswordChanged(password);
- }
- @Override public boolean keystorePut(String key, byte[] value) {
- Log.d(TAG, "keystorePut");
- return mKeyStore.put(key, value, KeyStore.UID_SELF, KeyStore.FLAG_NONE);
- }
- @Override public boolean keystoreImportKey(String key, byte[] value) {
- Log.d(TAG, "keystoreImport");
- return mKeyStore.importKey(key, value, KeyStore.UID_SELF, KeyStore.FLAG_NONE);
- }
-
@Override public void revokeAppPermission(final int uid, final String alias)
throws RemoteException {
Log.d(TAG, "revokeAppPermission");
@@ -102,16 +77,6 @@
});
}
- @Override public int attestKey(
- String alias, byte[] attestationChallenge,
- int[] idAttestationFlags) throws RemoteException {
- KeymasterCertificateChain attestationChain = new KeymasterCertificateChain();
- return performBlockingKeyChainCall(keyChainService -> {
- return keyChainService.attestKey(alias, attestationChallenge, idAttestationFlags,
- attestationChain);
- });
- }
-
@Override public boolean setKeyPairCertificate(String alias, byte[] userCertificate,
byte[] userCertificateChain) throws RemoteException {
return performBlockingKeyChainCall(keyChainService -> {
diff --git a/tests/Android.bp b/tests/Android.bp
index 823d7a1..8d2415a 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
android_test {
name: "KeyChainTests",
srcs: ["src/**/*.java"],
@@ -32,9 +36,9 @@
libs: [
"android.test.base",
],
- test_suites: ["general-tests"],
- required: [
- "KeyChainTestsSupport",
+ test_suites: ["device-tests"],
+ data: [
+ ":KeyChainTestsSupport",
],
instrumentation_for: "KeyChain",
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index c1f5021..754b76b 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -40,12 +40,14 @@
-->
<application>
<uses-library android:name="android.test.runner" />
- <activity android:name="com.android.keychain.tests.KeyChainTestActivity">
+ <activity android:name="com.android.keychain.tests.KeyChainTestActivity"
+ android:exported="true">
<intent-filter>
<action android:name="com.android.keychain.tests.KeyChainTestActivity"/>
</intent-filter>
</activity>
- <activity android:name="com.android.keychain.tests.KeyChainSocketTestActivity">
+ <activity android:name="com.android.keychain.tests.KeyChainSocketTestActivity"
+ android:exported="true">
<intent-filter>
<action android:name="com.android.keychain.tests.KeyChainSocketTestActivity"/>
</intent-filter>
diff --git a/tests/src/com/android/keychain/tests/BasicKeyChainServiceTest.java b/tests/src/com/android/keychain/tests/BasicKeyChainServiceTest.java
index 9d69482..f3f20db 100644
--- a/tests/src/com/android/keychain/tests/BasicKeyChainServiceTest.java
+++ b/tests/src/com/android/keychain/tests/BasicKeyChainServiceTest.java
@@ -18,6 +18,7 @@
import static android.os.Process.WIFI_UID;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.testng.Assert.assertThrows;
@@ -36,13 +37,16 @@
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore.ParcelableKeyGenParameterSpec;
+import android.util.Base64;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.keychain.tests.support.IKeyChainServiceTestSupport;
import java.io.IOException;
+import java.security.KeyStore;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import libcore.java.security.TestKeyStore;
import org.junit.After;
@@ -135,8 +139,8 @@
public void testCanAccessKeyAfterGettingGrant()
throws RemoteException, IOException, CertificateException {
Log.d(TAG, "Testing access to imported key after getting grant.");
- assertThat(mTestSupportService.keystoreReset()).isTrue();
- installFirstKey();
+ removeKeyPair(ALIAS_1);
+ generateRsaKey(ALIAS_1);
assertThat(mKeyChainService.requestPrivateKey(ALIAS_1)).isNull();
mTestSupportService.grantAppPermission(Process.myUid(), ALIAS_1);
assertThat(mKeyChainService.requestPrivateKey(ALIAS_1)).isNotNull();
@@ -146,7 +150,7 @@
public void testInstallAndRemoveKeyPair()
throws RemoteException, IOException, CertificateException {
Log.d(TAG, "Testing importing key.");
- assertThat(mTestSupportService.keystoreReset()).isTrue();
+ removeKeyPair(ALIAS_IMPORTED);
// No key installed, all should fail.
assertThat(mKeyChainService.requestPrivateKey(ALIAS_IMPORTED)).isNull();
assertThat(mKeyChainService.getCertificate(ALIAS_IMPORTED)).isNull();
@@ -159,10 +163,11 @@
Credentials.convertToPem(privateKeyEntry.getCertificateChain()),
ALIAS_IMPORTED)).isTrue();
- // No grant, all should still fail.
+ // No grant, Private key access should still fail.
assertThat(mKeyChainService.requestPrivateKey(ALIAS_IMPORTED)).isNull();
- assertThat(mKeyChainService.getCertificate(ALIAS_IMPORTED)).isNull();
- assertThat(mKeyChainService.getCaCertificates(ALIAS_IMPORTED)).isNull();
+ // Certificate access succeeds because this test runs as AID_SYSTEM.
+ assertThat(mKeyChainService.getCertificate(ALIAS_IMPORTED)).isNotNull();
+ assertThat(mKeyChainService.getCaCertificates(ALIAS_IMPORTED)).isNotNull();
// Grant access
mTestSupportService.grantAppPermission(Process.myUid(), ALIAS_IMPORTED);
// Has grant, all should succeed.
@@ -176,7 +181,7 @@
@Test
public void testUserSelectability() throws RemoteException, IOException, CertificateException {
Log.d(TAG, "Testing user-selectability of a key.");
- assertThat(mTestSupportService.keystoreReset()).isTrue();
+ removeKeyPair(ALIAS_IMPORTED);
PrivateKeyEntry privateKeyEntry =
TestKeyStore.getClientCertificate().getPrivateKey("RSA", "RSA");
assertThat(mTestSupportService.installKeyPair(privateKeyEntry.getPrivateKey().getEncoded(),
@@ -207,18 +212,6 @@
}
@Test
- public void testGenerateKeyPairErrorsOnSuperflousAttestationChallenge() throws RemoteException {
- KeyGenParameterSpec specWithChallenge =
- new KeyGenParameterSpec.Builder(buildRsaKeySpec(ALIAS_GENERATED))
- .setAttestationChallenge(DUMMY_CHALLENGE)
- .build();
- ParcelableKeyGenParameterSpec parcelableSpec =
- new ParcelableKeyGenParameterSpec(specWithChallenge);
- assertThat(mTestSupportService.generateKeyPair("RSA", parcelableSpec)).isEqualTo(
- KeyChain.KEY_GEN_SUPERFLUOUS_ATTESTATION_CHALLENGE);
- }
-
- @Test
public void testGenerateKeyPairErrorsOnInvalidAlgorithm() throws RemoteException {
ParcelableKeyGenParameterSpec parcelableSpec = new ParcelableKeyGenParameterSpec(
buildRsaKeySpec(ALIAS_GENERATED));
@@ -247,31 +240,69 @@
assertThat(mKeyChainService.requestPrivateKey(ALIAS_GENERATED)).isNotNull();
}
- @Test
- public void testAttestKeyFailsOnMissingChallenge() throws RemoteException {
- generateRsaKey(ALIAS_GENERATED);
- assertThat(mTestSupportService.attestKey(ALIAS_GENERATED, null, new int[]{}
- )).isEqualTo(KeyChain.KEY_ATTESTATION_MISSING_CHALLENGE);
- }
+ private static final String USER_CERT1 =
+ "MIIC+DCCAeACAQEwDQYJKoZIhvcNAQELBQAwRTELMAkGA1UEBhMCVVMxEzARBgNV" +
+ "BAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0" +
+ "ZDAeFw0yMTAyMTAwMDEyMDNaFw00ODA2MjgwMDEyMDNaMD8xCzAJBgNVBAYTAlVT" +
+ "MQswCQYDVQQIDAJDQTEUMBIGA1UEBwwLTWVkaW9jcmV0b24xDTALBgNVBAoMBENv" +
+ "Q28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2LieaIDILjZPtNvZI" +
+ "I6VSvKIPIdY5STadoWjmt9TqjyvszJ+oEceBPqwevOdtXpJCiHhP91B314XBT33z" +
+ "8re1b0A5k31YKcy0PU+3OMh4XG6O3/Z5/9GfsekfQZK3jagbn3uqJ2emyj0JK+HY" +
+ "ipD6iwyO21DerUYavPpC0uo8PKAxc9l6XjILg9qoi68yCi8P3tkLLAFWCsc7GUkA" +
+ "v97zUjGq8iz/gIrwmzJBB3O//7e1nuHO5AswgJiwWa9aY6uHKWm97xP0Kw4pShFI" +
+ "eEsdNKj82FURpWIvrWsztYPaEM/+nG8cNiJ1XRT3DfnlKzXVVQi7pE82HkUpFNLQ" +
+ "4wCbAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAJwq05ykEDK5etwC5jDYsX7hyiYI" +
+ "TAephAfhMqT7CtwScVlurSWt4ivv3IBx2huibQqdwq9zyCG9UzQKACMrEAaINerr" +
+ "VPwq+MpfgOUJRlZOCchr1KaN+quoOstStsGqemHZSTyUow6OrG2a+DIGQZy84yza" +
+ "y4+NZYtTjbVKFAmQkji344cVd6qFjCG5Dgo+1u4+YOWV4QTVWwbjgjCGiDNyZKeO" +
+ "Y8n8pdsv4ygat4VCotch22bqiUJLgY/abiprqKz3jfkko9urHkWsrdqzc3mS29AC" +
+ "tWVhomOQq/51A0wfQRbSw2MY+j5LaF22Sz5rnPk0u6Vcm2eLUI5gSJ/HZOU=";
- @Test
- public void testAttestKeyFailsOnNonExistentKey() throws RemoteException {
- assertThat(mTestSupportService.attestKey(ALIAS_NON_EXISTING, DUMMY_CHALLENGE, new int[]{}
- )).isEqualTo(KeyChain.KEY_ATTESTATION_FAILURE);
- }
+ private static final String USER_CERT2 =
+ "MIIDETCCAfkCFDEBBiFSwkBDAZMyhMjrr7P4wHklMA0GCSqGSIb3DQEBCwUAMEUx" +
+ "CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl" +
+ "cm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjEwMjIyMTgwNzE2WhcNNDgwNzEwMTgw" +
+ "NzE2WjBFMQswCQYDVQQGEwJVUzETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UE" +
+ "CgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOC" +
+ "AQ8AMIIBCgKCAQEA8XLlpvOvG22fJ4ZNSEdc/QIzkbjmtP5rmqX8dgfZdZ7xdUNO" +
+ "24Coyv9f+GiXJdDlL2+KIK3ZTpcuVdd5oO+GdrxB1Dnvhqt2YgOb/+VyeMOdsEre" +
+ "Bqw4Y+v8A92zpWlYlh9xyjVE53ismdQg2RT8kIxQt2ydMyZRx8KzNmZRsXouiVSi" +
+ "ngWlXdtkpXNiVt5CkSBwscfVNMkF4EfcKqYGLJzFTXcSsRVZoknGNtgJZIsHaOJh" +
+ "etmrfxATcbvNYdDhm9xs2ud/WwaRDqbMme9KVOrk9g5NBZIn7SpWDUyBk36W2CgQ" +
+ "U/3OPnDOUS6oW+YKE1xvJ3i2FhLD2ufJyNh4WwIDAQABMA0GCSqGSIb3DQEBCwUA" +
+ "A4IBAQDkl+8ZlYgKwSHjqxHjJsoBXBlwWUw0FQDaFz0tlPv/f5w80NsTXImVgRcP" +
+ "MBWxLrVa/JKCm7GPZgIotvYsRKL0/DRZDUuva86C2hi9C/E7OtgFkDO0d/t9QIQM" +
+ "gSNgdEgda/+lixc7XrsjJKAufFlYhzk/q5uyiD3SbqFzdlADumWtY9Xu6fqU2JB5" +
+ "TFOTxqpU5c2b+sXL6uc+dA2pSP94wL+7g+uKVhdJGsimbXIq1jl9r+C2ykrUjuz+" +
+ "b7uBJ5Qzq81tdmNCZ4pLtqmatTAMj2LYsKiXUe9Fh0nd/2aZek6I1YHaIBGg7jns" +
+ "ZYEWPj4Rd0KiE3L/ymeQ4VQx6SW2";
- @Test
- public void testAttestKeySucceedsOnGeneratedKey() throws RemoteException {
- generateRsaKey(ALIAS_GENERATED);
- assertThat(mTestSupportService.attestKey(ALIAS_GENERATED, DUMMY_CHALLENGE,
- null)).isEqualTo(KeyChain.KEY_ATTESTATION_SUCCESS);
- }
+ private static final String CA_CERT =
+ "MIIDazCCAlOgAwIBAgIUVA2nyBJMc/OcO0C/yPt/E1TwREIwDQYJKoZIhvcNAQEL" +
+ "BQAwRTELMAkGA1UEBhMCVVMxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM" +
+ "GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMTAyMDkyMzAzMzdaFw00ODA2" +
+ "MjcyMzAzMzdaMEUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw" +
+ "HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB" +
+ "AQUAA4IBDwAwggEKAoIBAQDxcuWm868bbZ8nhk1IR1z9AjORuOa0/muapfx2B9l1" +
+ "nvF1Q07bgKjK/1/4aJcl0OUvb4ogrdlOly5V13mg74Z2vEHUOe+Gq3ZiA5v/5XJ4" +
+ "w52wSt4GrDhj6/wD3bOlaViWH3HKNUTneKyZ1CDZFPyQjFC3bJ0zJlHHwrM2ZlGx" +
+ "ei6JVKKeBaVd22Slc2JW3kKRIHCxx9U0yQXgR9wqpgYsnMVNdxKxFVmiScY22Alk" +
+ "iwdo4mF62at/EBNxu81h0OGb3Gza539bBpEOpsyZ70pU6uT2Dk0FkiftKlYNTIGT" +
+ "fpbYKBBT/c4+cM5RLqhb5goTXG8neLYWEsPa58nI2HhbAgMBAAGjUzBRMB0GA1Ud" +
+ "DgQWBBSsg7KE2+ypUJrrk9pBIVYpgjZM3TAfBgNVHSMEGDAWgBSsg7KE2+ypUJrr" +
+ "k9pBIVYpgjZM3TAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBx" +
+ "pUWwTL8vsrq3hNEDOui1Y6kYPiaAs2Fd//tBjPhgc9tlKS8KLkGU0yW8kEyPovvK" +
+ "M6/IUDZhrHzli8Y9iJxiZ43o/fni1DOLHLU+CCOaKGrmBDBhgDlz1m2nXsCOb8uF" +
+ "ohz4Yp1SLMy1YpQPmltiaILXNTw33B8xFa0d9ChZbBudyiiNs0vijdFMMYetqAGM" +
+ "aXCjPXdM6hGjHxc6vf2ECJOrfVg3D22VOJ0WzrrJDM9pRxJgJ/IFG0eirKvE7G8s" +
+ "zk1Og79JAFr3qMKEnUS7nuym7J69HSQlFHc7JEMzeS78YR7EHOlOlK/bEX/8cHF9" +
+ "ZlGCOS1Ds0rmVe3CoQIp";
@Test
public void testSetKeyPairCertificate() throws RemoteException {
generateRsaKey(ALIAS_GENERATED);
- final byte[] userCert = new byte[] {'a', 'b', 'c'};
- final byte[] certChain = new byte[] {'d', 'e', 'f'};
+ final byte[] userCert = Base64.decode(USER_CERT1, Base64.DEFAULT);
+ final byte[] certChain = Base64.decode(CA_CERT, Base64.DEFAULT);
assertThat(mTestSupportService.setKeyPairCertificate(ALIAS_GENERATED, userCert,
certChain)).isTrue();
@@ -280,7 +311,8 @@
assertThat(mKeyChainService.getCertificate(ALIAS_GENERATED)).isEqualTo(userCert);
assertThat(mKeyChainService.getCaCertificates(ALIAS_GENERATED)).isEqualTo(certChain);
- final byte[] newUserCert = new byte[] {'x', 'y', 'z'};
+ final byte[] newUserCert = Base64.decode(USER_CERT2, Base64.DEFAULT);
+
assertThat(mTestSupportService.setKeyPairCertificate(ALIAS_GENERATED, newUserCert,
null)).isTrue();
assertThat(mKeyChainService.getCertificate(ALIAS_GENERATED)).isEqualTo(newUserCert);
@@ -364,35 +396,54 @@
}
}
- void installFirstKey() throws RemoteException, IOException, CertificateException {
- String intermediate = "-intermediate";
- String root = "-root";
+ @Test
+ public void testContainsKeyPair_NonExisting() throws RemoteException {
+ assertThat(mKeyChainService.containsKeyPair(ALIAS_NON_EXISTING)).isFalse();
+ }
- String alias1PrivateKey = Credentials.USER_PRIVATE_KEY + ALIAS_1;
- String alias1ClientCert = Credentials.USER_CERTIFICATE + ALIAS_1;
- String alias1IntermediateCert = (Credentials.CA_CERTIFICATE + ALIAS_1 + intermediate);
- String alias1RootCert = (Credentials.CA_CERTIFICATE + ALIAS_1 + root);
- PrivateKeyEntry privateKeyEntry =
+ @Test
+ public void testContainsKeyPair_ImportedKey() throws Exception {
+ installTestRsaKey();
+ assertThat(mKeyChainService.containsKeyPair(ALIAS_IMPORTED)).isTrue();
+ }
+
+ @Test
+ public void testContainsKeyPair_RemovedKey() throws Exception {
+ installTestRsaKey();
+ mKeyChainService.removeKeyPair(ALIAS_IMPORTED);
+ assertThat(mKeyChainService.containsKeyPair(ALIAS_IMPORTED)).isFalse();
+ }
+
+ @Test
+ public void testGetGrants_NonExisting() {
+ assertThrows(IllegalArgumentException.class,
+ () -> mKeyChainService.getGrants(ALIAS_NON_EXISTING));
+ }
+
+ @Test
+ public void testGetGrants_Empty() throws Exception {
+ installTestRsaKey();
+
+ assertThat(mKeyChainService.getGrants(ALIAS_IMPORTED)).isEmpty();
+ }
+
+ @Test
+ public void testGetGrants_NonEmpty() throws Exception {
+ final int uid = Process.myUid();
+
+ installTestRsaKey();
+ mTestSupportService.grantAppPermission(uid, ALIAS_IMPORTED);
+
+ assertThat(mKeyChainService.getGrants(ALIAS_IMPORTED)).isEqualTo(new int[] {uid});
+ }
+
+ private void installTestRsaKey() throws Exception {
+ final PrivateKeyEntry privateKeyEntry =
TestKeyStore.getClientCertificate().getPrivateKey("RSA", "RSA");
- Certificate intermediate1 = privateKeyEntry.getCertificateChain()[1];
- Certificate root1 = TestKeyStore.getClientCertificate().getRootCertificate("RSA");
-
- assertThat(
- mTestSupportService.keystoreImportKey(
- alias1PrivateKey, privateKeyEntry.getPrivateKey().getEncoded()))
- .isTrue();
- assertThat(
- mTestSupportService.keystorePut(
- alias1ClientCert,
- Credentials.convertToPem(privateKeyEntry.getCertificate())))
- .isTrue();
- assertThat(
- mTestSupportService.keystorePut(
- alias1IntermediateCert, Credentials.convertToPem(intermediate1)))
- .isTrue();
- assertThat(
- mTestSupportService.keystorePut(alias1RootCert, Credentials.convertToPem(root1)))
- .isTrue();
+ mTestSupportService.installKeyPair(privateKeyEntry.getPrivateKey().getEncoded(),
+ privateKeyEntry.getCertificate().getEncoded(),
+ Credentials.convertToPem(privateKeyEntry.getCertificateChain()),
+ ALIAS_IMPORTED);
}
void waitForSupportService() {
diff --git a/tests/src/com/android/keychain/tests/KeyChainActivityTest.java b/tests/src/com/android/keychain/tests/KeyChainActivityTest.java
index 73c3867..9acb6de 100644
--- a/tests/src/com/android/keychain/tests/KeyChainActivityTest.java
+++ b/tests/src/com/android/keychain/tests/KeyChainActivityTest.java
@@ -17,40 +17,41 @@
package com.android.keychain.tests;
import static com.android.keychain.KeyChainActivity.CertificateParametersFilter;
+
import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
import android.platform.test.annotations.LargeTest;
-import android.security.Credentials;
-import android.security.KeyStore;
import android.util.Base64;
+
import androidx.test.runner.AndroidJUnit4;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStreamWriter;
-import java.io.Writer;
-import java.nio.charset.StandardCharsets;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
-import javax.security.auth.x500.X500Principal;
-import org.bouncycastle.util.io.pem.PemObject;
-import org.bouncycastle.util.io.pem.PemWriter;
+
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.KeyFactory;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.CancellationException;
+
+import javax.security.auth.x500.X500Principal;
+
@LargeTest
@RunWith(AndroidJUnit4.class)
public final class KeyChainActivityTest {
@@ -58,22 +59,25 @@
// openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 3650
// -out root_ca_certificate.pem
private static final String ROOT_CA_CERT_RSA =
- "MIIDazCCAlOgAwIBAgIUWQjj+9olDNtdjcSLzK2RpxI9j6UwDQYJKoZIhvcNAQELBQAwRTELMAkG" +
- "A1UEBhMCVUsxCzAJBgNVBAgMAk5BMQ8wDQYDVQQHDAZMb25kb24xGDAWBgNVBAoMD0FuZHJvaWQg" +
- "VGVzdCBDQTAeFw0yMDAzMTIxMTUxNDJaFw0zMDAzMTAxMTUxNDJaMEUxCzAJBgNVBAYTAlVLMQsw" +
- "CQYDVQQIDAJOQTEPMA0GA1UEBwwGTG9uZG9uMRgwFgYDVQQKDA9BbmRyb2lkIFRlc3QgQ0EwggEi" +
- "MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGbsQO+LqPMr5Nr4Lq0B7C0th93phohSY6hb2w" +
- "MmZs3MRwamlw8FS64KEgmszX5lnyLNqRs91FOFNuq4f2A+TYQhawi9D2bHB7z2ishDM3SxNAqwQl" +
- "LzVNBJw7DAtimajy3VvXoprescFbsOZx8wPGGb2xMKqAXg4Yw9F6te4Y4BSIiwCWtammiSR8Ev0B" +
- "lcnMBrWmSZ4yYF+UgNgNiD/TVrTtRmzQlRhBo5n4F61SGeAxb5p0NRRGmAXKtx358HiLANzZSCiM" +
- "UE5IrgDvW8AKPn5InuYS1G1K2wG5ar1eanQahimtaIEugQxhqG0+/OiKKq2LGRiBpwV1OomXHNFr" +
- "AgMBAAGjUzBRMB0GA1UdDgQWBBRrxYWzKZpCHDi/NK4keXIU5iGukzAfBgNVHSMEGDAWgBRrxYWz" +
- "KZpCHDi/NK4keXIU5iGukzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBJTjUY" +
- "rfi3adJFpF2IFkSnavPRxi+NX6wxfKgQvcScO7sV18gAMG7r2NhjsVeScZ48mxsNkj99lHaVoNm6" +
- "c+sWmcb3LO7WCqmAgfJcHeQ5VuluwNoJWo+SGuKbh6/yRejNeQFf++uaEwXP3yNydwKJyQDyDwoG" +
- "vx0jvy8glkVl3fr6u0lGQqmubGU5Q1X6QyA0zJ/sSWBVorLCgk6KvPABQJhjoij+g/GOB1h4g6fb" +
- "bQ3xnek6TGwjQ1bB7rQlqBF7iP/9iUtuuDf0cR8LwMr1Z2OUEMDjRHQCZQJ3APc0kW1ewJ8nqQ+m" +
- "NsUKFRuThYtE/OFsV/TfXwMXbc2rMug+";
+ "MIIDazCCAlOgAwIBAgIUKNVj8g/LG+6tqsK8HZqT6EuktpkwDQYJKoZIhvcNAQEL" +
+ "BQAwRTELMAkGA1UEBhMCVUsxCzAJBgNVBAgMAk5BMQ8wDQYDVQQHDAZMb25kb24x" +
+ "GDAWBgNVBAoMD0FuZHJvaWQgVGVzdCBDQTAeFw0yMTAyMjMwMDU0MzdaFw0zMTAy" +
+ "MjEwMDU0MzdaMEUxCzAJBgNVBAYTAlVLMQswCQYDVQQIDAJOQTEPMA0GA1UEBwwG" +
+ "TG9uZG9uMRgwFgYDVQQKDA9BbmRyb2lkIFRlc3QgQ0EwggEiMA0GCSqGSIb3DQEB" +
+ "AQUAA4IBDwAwggEKAoIBAQDLpcaoJijYhS3QUDgG8kVGrwTxaVTS0TE156fJa5za" +
+ "s3RI7TYKHzYwn1KMJJUwoc+cOkv+rFC7j5MQ+6SMK2GpDoCoGn4FV9dDPVnxIgjj" +
+ "/66kuf+we1ur3gz7m/8tFFdZhLFoFMRzcNg+F35jSur8y0dnc8O83gMwuf+91pU7" +
+ "HyNahHzDyMM5sR7u1K91R1MKiOnVqJNTHWVK+rl3G0m0rbDTz7/xbq3/FPBvw764" +
+ "QUJgkSEG15i5CFNn2ww5IYnF30Wbke3kUfdd/Q32MOyfkcp3El/TPJj/3mevGHed" +
+ "vTi0j6ovIXMrDnoJvmeI40p97EMIlaZ3x39i+krE02RfAgMBAAGjUzBRMB0GA1Ud" +
+ "DgQWBBRDpNR2iik8NGDmY5IvgiUy6dMRMjAfBgNVHSMEGDAWgBRDpNR2iik8NGDm" +
+ "Y5IvgiUy6dMRMjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQDG" +
+ "OIZdoopwX2PlkAnWMR6PIl4GJ9eiPg1cbQHHcqsB8oNYiiiZydJ8TniZYFptEIjQ" +
+ "LIuerSr/a+c353JNOOK76w0vciJld8uQDCTnMChUahJsQjJJgDCyhjkQqV9Srstu" +
+ "IClm5IP/LEYVSzfR+LqhwX5JYUU8zsDnbiAH3CIENhBw5ApXgfDHP54bHOBw5eHE" +
+ "ETATnib8uPZVHsyHQXXULDqMITY9pmjc9/9x6CqcMGEiSVvhDSujerVlnw3I17Te" +
+ "HWT8bf8mJyQFR8kr59NPQMNN4oUIfFzj2VgzjL21mKGR+hBGqoIMJ1ZdPGCJsw0W" +
+ "5WYM4TtQtL7yNVDtJzOL";
// Generated with:
// openssl genrsa -out intermediate_key.pem 2048
@@ -81,42 +85,74 @@
// openssl x509 -req -days 3650 -in intermediate_ca.csr -CA root_ca_certificate.pem
// -CAkey key.pem -CAcreateserial -out intermediate_ca.pem
private static final String INTERMEDIATE_CA_CERT_RSA =
- "MIIDHjCCAgYCFHVrA0CEKcoc/C8BqKap3a1m0x8CMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNVBAYT" +
- "AlVLMQswCQYDVQQIDAJOQTEPMA0GA1UEBwwGTG9uZG9uMRgwFgYDVQQKDA9BbmRyb2lkIFRlc3Qg" +
- "Q0EwHhcNMjAwMzEyMTE1NzUwWhcNMzAwMzEwMTE1NzUwWjBSMQswCQYDVQQGEwJVSzELMAkGA1UE" +
- "CAwCTkExDzANBgNVBAcMBkxvbmRvbjElMCMGA1UECgwcQW5kcm9pZCBJbnRlcm1lZGlhdGUgVGVz" +
- "dCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPlEIzApeTyKzQWTvv25z/KVEbsr" +
- "alrcto7mX56HV1VQ53cGqi4I7dso3cvpg0CYfcZ+mZh6Evkd+njkcc7Dh/nI0KBJIzGZuo2LB+0r" +
- "qT0RfvI/Xv7CqLO9KOjWJ3+HK3EhSXGvnLvSTsQD1LnE9HXKVdhdOUgLFjbcZrzH62mvTRAO6nhg" +
- "agWTzprTXOX8okaMJtJl9QGMG63Z/m5DONPPrgASW6X6wksGjyorEaakQTUGuPimapP5mk+Y31Se" +
- "pLDDumqRavLT2CpfjHfFq0iDmnnJjG5nz6oKlirhg9JjxHwuKm5jIdsO4dIgi1fJ8Goz2ODG6R6E" +
- "6CjitsQjoMMCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAdud6PahLSmDLOTr9t0Jqq1HKpeRsjSqn" +
- "JTpd/GkNUrxcctKLTzpAruMq6en8OfcSEa2s3HUMJ1LVMoO9pp4aQaadH7626Q4uqHbNGHWngVNX" +
- "lfBioxrH2QJ2wuKBjUipEGWaM3LY0wqNuBFd5qAVuBwQtZ1x/XH7/Y4l38Y5EGGEi4jSw8eCqiiQ" +
- "2UKItmK8byl3/T5SVgAMbFYz1WJN37EgETEcEgPlosSQ4pha6fVB3Oz6mSfzjGXqHKpHBPUn/N2d" +
- "Z2kxJG8IuwhhhyemhqJdCfOxT2WpemgLQQCCgqtM9O89peWL8AJUVT9cF9KySOvh/P9lTtSf5bJf" +
- "sAzfSg==";
+ "MIIDHjCCAgYCFHgZBbZMuJTvvm1wlBBoPE7peS4jMA0GCSqGSIb3DQEBCwUAMEUx" +
+ "CzAJBgNVBAYTAlVLMQswCQYDVQQIDAJOQTEPMA0GA1UEBwwGTG9uZG9uMRgwFgYD" +
+ "VQQKDA9BbmRyb2lkIFRlc3QgQ0EwHhcNMjEwMjIzMDA1ODQ5WhcNMzEwMjIxMDA1" +
+ "ODQ5WjBSMQswCQYDVQQGEwJVSzELMAkGA1UECAwCTkExDzANBgNVBAcMBkxvbmRv" +
+ "bjElMCMGA1UECgwcQW5kcm9pZCBJbnRlcm1lZGlhdGUgVGVzdCBDQTCCASIwDQYJ" +
+ "KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMZ5tLvS8qiZGK8fBUPzZYWRG2kmf3Hi" +
+ "o6db8qjTvzkJ0l43zmI92v0pEy8a4/XlC7wGSaJuX/TqpAR43+a9kYqGWczkASpX" +
+ "7w1W5tjjDlLANZdRP2R7Wb3XWTTMxzDWOqsMGTHw+H92oFm4bGO9+PaBRRzifLmQ" +
+ "OI/Whw6/kHZlXyI7J68EwRbyaZXNJ6iqk8dF7B4iwZ9yNgW1H0m8uxdDAykEA2WX" +
+ "wbzITwD1zdMsQV7/eLe9RIMFN5VMeHtIFUi2AcioG0i4ZLZNFnVrFQKu7u3XQyxk" +
+ "u7ZzgeGtpluM71sDxnqhZv9NaZIq3mV3JrKHPsw6+uJjN4U5AVfzIYUCAwEAATAN" +
+ "BgkqhkiG9w0BAQsFAAOCAQEAXrRQUFlLyS3QlmwkGocLQISY9B8fF8LTH7sl6HFA" +
+ "VSVuhPDuNsmqVhsMH1981MY2rSVfM3fkUMz1WEH7ZbhooYPirax/AlW+oRdRB/xX" +
+ "WEAJRGgybK98PXogI4tqEvicVn2kfcyNzmfMn8yRClxD5GuZ0oOA50lpUwUmeJjo" +
+ "jb3DY8NF+bcA0lW5h7p86ezqjhB836XZRL47jZJj+jgKoiSsdcex7rzikW6bzdlV" +
+ "f9DCuBJMpM1y31AP15Gvg5Jhh9Wc4y8LLipTn7wGdJvvhclUe1U3roerhAEB+rU/" +
+ "Eu7h+Nqjogg3IzmfHlhDe4N8o/XLdc2FnhCZGZklDDDMvA==";
// Generated with:
// openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr
// openssl x509 -req -days 3650 -in server.csr -CA intermediate_ca.pem
// -CAkey intermediate_key.pem -CAcreateserial -out server.pem
private static final String LEAF_SERVER_CERT_RSA =
- "MIIDIjCCAgoCFGNstHCN7uzPtFlxnbu2FQ3+sc7rMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYT" +
- "AlVLMQswCQYDVQQIDAJOQTEPMA0GA1UEBwwGTG9uZG9uMSUwIwYDVQQKDBxBbmRyb2lkIEludGVy" +
- "bWVkaWF0ZSBUZXN0IENBMB4XDTIwMDMxMjEyMTQwMloXDTMwMDMxMDEyMTQwMlowSTELMAkGA1UE" +
- "BhMCVUsxCzAJBgNVBAgMAk5BMQ8wDQYDVQQHDAZMb25kb24xHDAaBgNVBAoME0FuZHJvaWQgU2Vy" +
- "dmVyIFRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCBdt0uV01nG2ULMThfnEx" +
- "CDoVvOSJJLcqKSMsoRUcEYvvqA70x7O2BR6+FZLilr09qAyK1ygyTh+Q6NcBxiB137khrygd/R3S" +
- "U9nNWI6Xj882EospCiPaewR3j49F6F45PvviAacx7v0mZpU1dVMP7ZvhJWb1AB675/379EFztYyU" +
- "ghM5ub8zuAgBuvH/O/dJpnmKUmO43n/qSHDM+Q+oDmLAkWD4gd3SOBZKcLgTyO/pD0pghT8m2t0t" +
- "9X/3KX+tQhif1pAemCOqvxr/HHjlLWxY+QWmcRIAMzTg6+h7sNSKAwwfulMomzMwzkV5sJAYUJOm" +
- "suhhyAXCNX5rWuqXAgMBAAEwDQYJKoZIhvcNAQELBQADggEBACdFD/uV4iT4jg3/rEtszPHkyP4b" +
- "zHaYJYpExoWNbJIz7djhycptdM7wjSoesWQMfpJz95aNWoVHrW85DSdGT+7HwZEsW1zUx3KkXURA" +
- "CdlbVBn1CS4vm0Xk7Rr3LayfhqdFALQVItvBr+LkJPiG/R1jQySp1qaw+NrwukFoepukeZxHH1bF" +
- "9zjGCLwOcfuRB9g8Gm45wRgoSUTDKbD1SMSLuPyllKeHLKE+chhYjm51Evy2xR0DLm0yaFmeyPPM" +
- "KQaZJmtkeAzk2uYYb1HsVDlnEoDoXpTKKN1j39qckpCHxF0X0KqbN7D0grDWIDIee5mnSrg3+Xq5" +
- "aCEDTUn6uU4=";
+ "MIIDIjCCAgoCFDV+Rg1WxBy4ThLxvu1lRJl1YUzwMA0GCSqGSIb3DQEBCwUAMFIx" +
+ "CzAJBgNVBAYTAlVLMQswCQYDVQQIDAJOQTEPMA0GA1UEBwwGTG9uZG9uMSUwIwYD" +
+ "VQQKDBxBbmRyb2lkIEludGVybWVkaWF0ZSBUZXN0IENBMB4XDTIxMDIyMzAxMDEw" +
+ "OFoXDTMxMDIyMTAxMDEwOFowSTELMAkGA1UEBhMCVUsxCzAJBgNVBAgMAk5BMQ8w" +
+ "DQYDVQQHDAZMb25kb24xHDAaBgNVBAoME0FuZHJvaWQgU2VydmVyIFRlc3QwggEi" +
+ "MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7267U6iCagBJFiYMMUkteQliO" +
+ "ljOfnuSZJTG1xxNXBggLDdHmchjOPQgEICINpxz7Hhg+PLME3DEqrwUHo9k/bR3e" +
+ "Tglt1o6qxGirIEKROtdzNyi3medRex6FATXq1g4W/1U0tl8EbMv9kJPZP52Uj0Rq" +
+ "XYZ9Y27IGtWDcudpyJij/nBbV/kfufti2pFNHhnXytyBrQXz6AziZjHnNt316z64" +
+ "Tfqchr0jxqIF3Sup9AVKnGooGymwT8ez5C6VO7WoRZnp40pH78GzSALnRJC9w26V" +
+ "1IVlqjYPWvFwJ0ENb0Nuc8Jr3tW/0gf1UzTsuRsMU9vl+tMjweXS8HI8M4uRAgMB" +
+ "AAEwDQYJKoZIhvcNAQELBQADggEBAIcOLAdu9HU6+Bk/SkQW2qW2mY5+WFSYYRG5" +
+ "KfDiMQr0kUeddBJYk+bLN3Qqi6KUAZ+ITxyarVrjZjxaTr1JV6m9fVxdZ8elAx+7" +
+ "ci9ghBkiUPKFtVz3Y1F31Em4tLRr0LHF49Mjvr62+mQuZlAXZ3TuMdxrwc9AePhN" +
+ "btY8YwUsPPJ2vrIQB14NJ6EIaMaIyTowBPX9eo5K47ISbfnCUKhFYAEK4v5s4Hkt" +
+ "Us/209E0FNdFKOZDKDwclhZSFbyA1tknBRXsP7QCFuj/hPFxcRaj9R8CoHQAqEFr" +
+ "mV8JscA9dV40m+kRkj5TZeHjIw2xI39btv5aeRW2fBNB0MEoNPQ=";
+
+ private static final String PRIVATE_SERVER_KEY =
+ "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC7267U6iCagBJF" +
+ "iYMMUkteQliOljOfnuSZJTG1xxNXBggLDdHmchjOPQgEICINpxz7Hhg+PLME3DEq" +
+ "rwUHo9k/bR3eTglt1o6qxGirIEKROtdzNyi3medRex6FATXq1g4W/1U0tl8EbMv9" +
+ "kJPZP52Uj0RqXYZ9Y27IGtWDcudpyJij/nBbV/kfufti2pFNHhnXytyBrQXz6Azi" +
+ "ZjHnNt316z64Tfqchr0jxqIF3Sup9AVKnGooGymwT8ez5C6VO7WoRZnp40pH78Gz" +
+ "SALnRJC9w26V1IVlqjYPWvFwJ0ENb0Nuc8Jr3tW/0gf1UzTsuRsMU9vl+tMjweXS" +
+ "8HI8M4uRAgMBAAECggEARWNUhYJhPpAVr6emRxPSkONysGAce1YGW+bYIKuCoj8x" +
+ "E1wsbrEwJmV2o4d27JIQa1TnYX2sJhxq8Lgq5HKJ2Rql0KoEY5S/p6Xaf3LwA5K3" +
+ "Z/A00vQ+8+LFGB2lW7NrCuWPBGRkXk8NXgBcC/+qZegxPhSDi6cBkVoQCXiUr4Zs" +
+ "4wacrmpQbl/FekvpuWQxnVAm95knZhJ87r7izwO6e3VP1VZseG7ld6PbxkLnuTP7" +
+ "Y9Q+viAPwkk1SedvYy6RtIRbyyOKzTVbh9SXirPsLM6N+a8k3J7LY+8HnZhte0O4" +
+ "BFoZwwhXt/kPHilro6gt69Bh2bas+lpP62c8e7Q3HQKBgQDwY6XJzLXV1UQNbwLL" +
+ "FHTDBdf3Afe6W5jcKpsOUaNP/424JeLpc5a2ccaytCPs2p3RCTZ3SHaxe+LNrKbU" +
+ "iKdloGO/XTqcAHbvw/uy5Zzsv6XqUjREmCvCatF7UU7PH9Pztjhb91IXT7taB1Ee" +
+ "yYyPPJU9ioZDEV45/PIUpGwjnwKBgQDIDrtHx5fBH0od8XWSBzuQ3QjLl3WZcEAv" +
+ "Bf2GcdYUV7migWOXm28k51l2VbQQRCVOulJjf45TZgZgzKInZZJ+rZ69FLrOx7q3" +
+ "7X+8rTyWKa2u/IlZ/EBtNIRuqH7A+OuY5qroFngtYa/qsaX58/eUAe64I6HslenG" +
+ "ktvmwdSCzwKBgAtQ1YQLU9/t+xcay6ndm6V2h/UDrbKjDy4F/2iMJUDlybkKZ4UP" +
+ "wN9zuaO94RcML3OgmGTDD3tJVqLR5sSIbkDVbPycGd8wEmk084s3TczDNL80AWvd" +
+ "Meoj9xpz+F69o8+MG1kQ6ldYlHwnbgUh/bDcbDYKaEmN7r6SDp80IjcHAoGAOrqQ" +
+ "Yf8G3qu3z1h94jN7Wgh5N4MsA7I/NU614Uzzwp8KINmJCg2YMCY2ThXUuV238gei" +
+ "fhEJEBSIVMxd4eDgg42mZu159ZAOkUYIVLQqcA6mLRN3otH5e9WJ9w5Bv5aTWxyE" +
+ "GYPXHcNqqCQkjF8BVBLJKIdVVqWfriqYoYJPR2MCgYAWWIrS8XN1c2iLParb+xOJ" +
+ "q1WKe8q5wFucqJCVFbJXjtpGgZFxZLAFlT8VpaiBwDLQBPC+CYhzLVwAvzW2h46W" +
+ "KONqO7zoxiuwOEj466iH4YrgviNw6lGtgSPB6wx91c7se/1lcZhviBMB5rUjtxxj" +
+ "qKIC9Y+gz77w1M3pwMlhDA==";
private static final X500Principal LEAF_SUBJECT =
new X500Principal("O=Android Server Test, L=London, ST=NA, C=UK");
@@ -127,44 +163,63 @@
private static final X500Principal ROOT_SUBJECT =
new X500Principal("O=Android Test CA, L=London, ST=NA, C=UK");
+ private byte[] mPrivateKey;
private byte[] mLeafRsaCertificate;
private byte[] mIntermediateRsaCertificate;
private byte[] mRootRsaCertificate;
@Before
public void setUp() {
+ mPrivateKey = Base64.decode(PRIVATE_SERVER_KEY, Base64.DEFAULT);
mLeafRsaCertificate = Base64.decode(LEAF_SERVER_CERT_RSA, Base64.DEFAULT);
mIntermediateRsaCertificate = Base64.decode(INTERMEDIATE_CA_CERT_RSA, Base64.DEFAULT);
mRootRsaCertificate = Base64.decode(ROOT_CA_CERT_RSA, Base64.DEFAULT);
}
+ @After
+ public void tearDown() {
+ KeyStore keyStore = null;
+ try {
+ keyStore = KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ keyStore.deleteEntry("testCertificateParametersFilter_client");
+ } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException
+ | IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
@Test
public void testCertificateParametersFilter_filtersByIntermediateIssuer()
- throws InterruptedException, ExecutionException, CancellationException,
- TimeoutException, IOException, CertificateEncodingException {
+ throws CancellationException, IOException, CertificateException,
+ KeyStoreException, NoSuchAlgorithmException {
KeyStore keyStore = prepareKeyStoreWithLongChainCertificates();
assertThat(createCheckerForIssuer(keyStore, ROOT_SUBJECT)
- .shouldPresentCertificate("client")).isTrue();
+ .shouldPresentCertificate("testCertificateParametersFilter_client")).isTrue();
assertThat(createCheckerForIssuer(keyStore, INTERMEDIATE_SUBJECT)
- .shouldPresentCertificate("client")).isTrue();
+ .shouldPresentCertificate("testCertificateParametersFilter_client")).isTrue();
assertThat(createCheckerForIssuer(keyStore, LEAF_SUBJECT)
- .shouldPresentCertificate("client")).isFalse();
+ .shouldPresentCertificate("testCertificateParametersFilter_client")).isFalse();
}
// Return a KeyStore instance that has both a client certificate as well as a certificate
// chain associated with it.
private KeyStore prepareKeyStoreWithLongChainCertificates()
- throws IOException, CertificateEncodingException {
- KeyStore keyStore = mock(KeyStore.class);
- when(keyStore.get(Credentials.USER_CERTIFICATE + "client")).thenReturn(mLeafRsaCertificate);
- Certificate[] intermediates = new Certificate[] {
- parseCertificate(mRootRsaCertificate), parseCertificate(mIntermediateRsaCertificate)};
- byte[] intermediatesPem = convertToPem(intermediates);
- assertThat(intermediatesPem).isNotNull();
- when(keyStore.get(Credentials.CA_CERTIFICATE + "client")).thenReturn(intermediatesPem);
+ throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException {
+
+ KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+
+ Certificate[] certs = new Certificate[3];
+ certs[0] = parseCertificate(mLeafRsaCertificate);
+ certs[1] = parseCertificate(mIntermediateRsaCertificate);
+ certs[2] = parseCertificate(mRootRsaCertificate);
+
+ keyStore.setKeyEntry("testCertificateParametersFilter_client", parseKey(mPrivateKey),
+ null, certs);
return keyStore;
}
@@ -189,16 +244,13 @@
}
}
- // Copied from android.security.Credentials, as that is a framework class.
- public static byte[] convertToPem(Certificate... objects)
- throws IOException, CertificateEncodingException {
- ByteArrayOutputStream bao = new ByteArrayOutputStream();
- Writer writer = new OutputStreamWriter(bao, StandardCharsets.US_ASCII);
- PemWriter pw = new PemWriter(writer);
- for (Certificate o : objects) {
- pw.writeObject(new PemObject("CERTIFICATE", o.getEncoded()));
+ private static PrivateKey parseKey(byte[] key) {
+ try {
+ KeyFactory kf = KeyFactory.getInstance("RSA");
+ return kf.generatePrivate(new PKCS8EncodedKeySpec(key));
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ fail(String.format("Could not parse private key: %s", e));
+ return null;
}
- pw.close();
- return bao.toByteArray();
}
}