[AndroidCrypto] Implement Root (read-only) and CurrentUser/My certificate stores...
authorElinor Fung <elfung@microsoft.com>
Fri, 19 Mar 2021 17:24:26 +0000 (10:24 -0700)
committerGitHub <noreply@github.com>
Fri, 19 Mar 2021 17:24:26 +0000 (10:24 -0700)
28 files changed:
src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.X509.cs
src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.X509Store.cs [new file with mode: 0644]
src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/CMakeLists.txt
src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_eckey.c
src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_eckey.h
src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_jni.c
src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_jni.h
src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_rsa.c
src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_rsa.h
src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_x509.c
src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_x509.h
src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_x509store.c [new file with mode: 0644]
src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_x509store.h [new file with mode: 0644]
src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Android/AndroidCertificatePal.cs
src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Android/StorePal.AndroidKeyStore.cs [new file with mode: 0644]
src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Android/StorePal.TrustedStore.cs [new file with mode: 0644]
src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Android/StorePal.cs
src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/DirectoryBasedStoreProvider.cs
src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/StorePal.cs
src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnsupportedDisallowedStore.cs [new file with mode: 0644]
src/libraries/System.Security.Cryptography.X509Certificates/src/Resources/Strings.resx
src/libraries/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj
src/libraries/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/X509Certificate.cs
src/libraries/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj
src/libraries/System.Security.Cryptography.X509Certificates/tests/X509StoreMutableTests.Android.cs [new file with mode: 0644]
src/libraries/System.Security.Cryptography.X509Certificates/tests/X509StoreMutableTests.OSX.cs
src/libraries/System.Security.Cryptography.X509Certificates/tests/X509StoreMutableTests.cs [new file with mode: 0644]
src/libraries/System.Security.Cryptography.X509Certificates/tests/X509StoreTests.cs

index 2ec104f..b40581f 100644 (file)
@@ -89,6 +89,7 @@ internal static partial class Interop
             DSA,
             EC,
             RSA,
+            UnknownAlgorithm = -1,
         }
 
         [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509PublicKey")]
diff --git a/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.X509Store.cs b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.X509Store.cs
new file mode 100644 (file)
index 0000000..52e12eb
--- /dev/null
@@ -0,0 +1,71 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using Microsoft.Win32.SafeHandles;
+
+internal static partial class Interop
+{
+    internal static partial class AndroidCrypto
+    {
+        [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509StoreAddCertificate")]
+        [return: MarshalAs(UnmanagedType.Bool)]
+        internal static extern unsafe bool X509StoreAddCertificate(
+            SafeX509StoreHandle store,
+            SafeX509Handle cert,
+            string hashString);
+
+        [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509StoreAddCertificateWithPrivateKey")]
+        [return: MarshalAs(UnmanagedType.Bool)]
+        internal static extern unsafe bool X509StoreAddCertificateWithPrivateKey(
+            SafeX509StoreHandle store,
+            SafeX509Handle cert,
+            SafeKeyHandle key,
+            PAL_KeyAlgorithm algorithm,
+            string hashString);
+
+        [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509StoreContainsCertificate")]
+        [return: MarshalAs(UnmanagedType.Bool)]
+        internal static extern unsafe bool X509StoreContainsCertificate(
+            SafeX509StoreHandle store,
+            SafeX509Handle cert,
+            string hashString);
+
+        [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509StoreEnumerateCertificates")]
+        [return: MarshalAs(UnmanagedType.Bool)]
+        internal static extern unsafe bool X509StoreEnumerateCertificates(
+            SafeX509StoreHandle storeHandle,
+            delegate* unmanaged<void*, void*, Interop.AndroidCrypto.PAL_KeyAlgorithm, void*, void> callback,
+            void *callbackContext);
+
+        [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509StoreEnumerateTrustedCertificates")]
+        [return: MarshalAs(UnmanagedType.Bool)]
+        internal static extern unsafe bool X509StoreEnumerateTrustedCertificates(
+            byte systemOnly,
+            delegate* unmanaged<void*, void*, void> callback,
+            void *callbackContext);
+
+        [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509StoreOpenDefault")]
+        internal static extern unsafe SafeX509StoreHandle X509StoreOpenDefault();
+
+        [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509StoreRemoveCertificate")]
+        [return: MarshalAs(UnmanagedType.Bool)]
+        internal static extern unsafe bool X509StoreRemoveCertificate(
+            SafeX509StoreHandle store,
+            SafeX509Handle cert,
+            string hashString);
+    }
+}
+
+namespace System.Security.Cryptography.X509Certificates
+{
+    internal sealed class SafeX509StoreHandle : Interop.JObjectLifetime.SafeJObjectHandle
+    {
+        public SafeX509StoreHandle()
+        {
+        }
+    }
+}
index d25b9a7..e3e6542 100644 (file)
@@ -19,7 +19,7 @@ EC_KEY* AndroidCryptoNative_NewEcKey(jobject curveParameters, jobject keyPair)
     return keyInfo;
 }
 
-EC_KEY* AndroidCryptoNative_NewEcKeyFromPublicKey(JNIEnv *env, jobject /*ECPublicKey*/ publicKey)
+EC_KEY* AndroidCryptoNative_NewEcKeyFromKeys(JNIEnv *env, jobject /*ECPublicKey*/ publicKey, jobject /*ECPrivateKey*/ privateKey)
 {
     assert(publicKey != NULL);
 
@@ -27,7 +27,7 @@ EC_KEY* AndroidCryptoNative_NewEcKeyFromPublicKey(JNIEnv *env, jobject /*ECPubli
         return NULL;
 
     jobject curveParameters = (*env)->CallObjectMethod(env, publicKey, g_ECPublicKeyGetParams);
-    return AndroidCryptoNative_NewEcKey(ToGRef(env, curveParameters), AndroidCryptoNative_CreateKeyPair(env, publicKey, NULL));
+    return AndroidCryptoNative_NewEcKey(ToGRef(env, curveParameters), AndroidCryptoNative_CreateKeyPair(env, publicKey, privateKey));
 }
 
 #pragma clang diagnostic push
index 14692ab..2127155 100644 (file)
@@ -16,7 +16,7 @@ typedef struct EC_KEY
 } EC_KEY;
 
 EC_KEY* AndroidCryptoNative_NewEcKey(jobject curveParameters, jobject keyPair);
-EC_KEY* AndroidCryptoNative_NewEcKeyFromPublicKey(JNIEnv *env, jobject /*ECPublicKey*/ publicKey);
+EC_KEY* AndroidCryptoNative_NewEcKeyFromKeys(JNIEnv *env, jobject /*ECPublicKey*/ publicKey, jobject /*ECPrivateKey*/ privateKey);
 
 /*
 Cleans up and deletes an EC_KEY instance.
index d35b2c4..bd63173 100644 (file)
@@ -85,6 +85,12 @@ jmethodID g_sslCtxGetDefaultSslParamsMethod;
 jclass    g_GCMParameterSpecClass;
 jmethodID g_GCMParameterSpecCtor;
 
+// java/security/interfaces/DSAKey
+jclass    g_DSAKeyClass;
+
+// java/security/interfaces/ECKey
+jclass    g_ECKeyClass;
+
 // java/security/interfaces/RSAKey
 jclass    g_RSAKeyClass;
 jmethodID g_RSAKeyGetModulus;
@@ -106,6 +112,27 @@ jmethodID g_keyPairGenInitializeWithParamsMethod;
 jmethodID g_keyPairGenInitializeMethod;
 jmethodID g_keyPairGenGenKeyPairMethod;
 
+// java/security/KeyStore
+jclass    g_KeyStoreClass;
+jmethodID g_KeyStoreGetInstance;
+jmethodID g_KeyStoreAliases;
+jmethodID g_KeyStoreContainsAlias;
+jmethodID g_KeyStoreDeleteEntry;
+jmethodID g_KeyStoreGetCertificate;
+jmethodID g_KeyStoreGetEntry;
+jmethodID g_KeyStoreLoad;
+jmethodID g_KeyStoreSetCertificateEntry;
+jmethodID g_KeyStoreSetKeyEntry;
+
+// java/security/KeyStore$PrivateKeyEntry
+jclass    g_PrivateKeyEntryClass;
+jmethodID g_PrivateKeyEntryGetCertificate;
+jmethodID g_PrivateKeyEntryGetPrivateKey;
+
+// java/security/KeyStore$TrustedCertificateEntry
+jclass    g_TrustedCertificateEntryClass;
+jmethodID g_TrustedCertificateEntryGetTrustedCertificate;
+
 // java/security/Signature
 jclass    g_SignatureClass;
 jmethodID g_SignatureGetInstance;
@@ -130,6 +157,7 @@ jmethodID g_CertPathGetEncoded;
 
 // java/security/cert/X509Certificate
 jclass    g_X509CertClass;
+jmethodID g_X509CertEquals;
 jmethodID g_X509CertGetEncoded;
 jmethodID g_X509CertGetPublicKey;
 
@@ -254,6 +282,11 @@ jmethodID g_CollectionSize;
 jclass    g_DateClass;
 jmethodID g_DateGetTime;
 
+// java/util/Enumeration
+jclass    g_Enumeration;
+jmethodID g_EnumerationHasMoreElements;
+jmethodID g_EnumerationNextElement;
+
 // java/util/Iterator
 jclass    g_IteratorClass;
 jmethodID g_IteratorHasNext;
@@ -537,9 +570,14 @@ JNI_OnLoad(JavaVM *vm, void *reserved)
     g_CertPathClass =       GetClassGRef(env, "java/security/cert/CertPath");
     g_CertPathGetEncoded =  GetMethod(env, false, g_CertPathClass, "getEncoded", "(Ljava/lang/String;)[B");
 
-    g_X509CertClass =                       GetClassGRef(env, "java/security/cert/X509Certificate");
-    g_X509CertGetEncoded =                  GetMethod(env, false, g_X509CertClass, "getEncoded", "()[B");
-    g_X509CertGetPublicKey =                GetMethod(env, false, g_X509CertClass, "getPublicKey", "()Ljava/security/PublicKey;");
+    g_X509CertClass =           GetClassGRef(env, "java/security/cert/X509Certificate");
+    g_X509CertEquals =          GetMethod(env, false, g_X509CertClass, "equals", "(Ljava/lang/Object;)Z");
+    g_X509CertGetEncoded =      GetMethod(env, false, g_X509CertClass, "getEncoded", "()[B");
+    g_X509CertGetPublicKey =    GetMethod(env, false, g_X509CertClass, "getPublicKey", "()Ljava/security/PublicKey;");
+
+    g_DSAKeyClass = GetClassGRef(env, "java/security/interfaces/DSAKey");
+
+    g_ECKeyClass =  GetClassGRef(env, "java/security/interfaces/ECKey");
 
     g_RSAKeyClass =                    GetClassGRef(env, "java/security/interfaces/RSAKey");
     g_RSAKeyGetModulus =               GetMethod(env, false, g_RSAKeyClass, "getModulus", "()Ljava/math/BigInteger;");
@@ -558,6 +596,24 @@ JNI_OnLoad(JavaVM *vm, void *reserved)
     g_keyPairGenInitializeWithParamsMethod = GetMethod(env, false, g_keyPairGenClass, "initialize", "(Ljava/security/spec/AlgorithmParameterSpec;)V");
     g_keyPairGenGenKeyPairMethod =           GetMethod(env, false, g_keyPairGenClass, "genKeyPair", "()Ljava/security/KeyPair;");
 
+    g_KeyStoreClass =               GetClassGRef(env, "java/security/KeyStore");
+    g_KeyStoreGetInstance =         GetMethod(env, true, g_KeyStoreClass, "getInstance", "(Ljava/lang/String;)Ljava/security/KeyStore;");
+    g_KeyStoreAliases =             GetMethod(env, false, g_KeyStoreClass, "aliases", "()Ljava/util/Enumeration;");
+    g_KeyStoreContainsAlias =       GetMethod(env, false, g_KeyStoreClass, "containsAlias", "(Ljava/lang/String;)Z");
+    g_KeyStoreDeleteEntry =         GetMethod(env, false, g_KeyStoreClass, "deleteEntry", "(Ljava/lang/String;)V");
+    g_KeyStoreGetCertificate =      GetMethod(env, false, g_KeyStoreClass, "getCertificate", "(Ljava/lang/String;)Ljava/security/cert/Certificate;");
+    g_KeyStoreGetEntry =            GetMethod(env, false, g_KeyStoreClass, "getEntry", "(Ljava/lang/String;Ljava/security/KeyStore$ProtectionParameter;)Ljava/security/KeyStore$Entry;");
+    g_KeyStoreLoad =                GetMethod(env, false, g_KeyStoreClass, "load", "(Ljava/io/InputStream;[C)V");
+    g_KeyStoreSetCertificateEntry = GetMethod(env, false, g_KeyStoreClass, "setCertificateEntry", "(Ljava/lang/String;Ljava/security/cert/Certificate;)V");
+    g_KeyStoreSetKeyEntry =         GetMethod(env, false, g_KeyStoreClass, "setKeyEntry", "(Ljava/lang/String;Ljava/security/Key;[C[Ljava/security/cert/Certificate;)V");
+
+    g_PrivateKeyEntryClass =            GetClassGRef(env, "java/security/KeyStore$PrivateKeyEntry");
+    g_PrivateKeyEntryGetCertificate =   GetMethod(env, false, g_PrivateKeyEntryClass, "getCertificate", "()Ljava/security/cert/Certificate;");
+    g_PrivateKeyEntryGetPrivateKey =    GetMethod(env, false, g_PrivateKeyEntryClass, "getPrivateKey", "()Ljava/security/PrivateKey;");
+
+    g_TrustedCertificateEntryClass =                    GetClassGRef(env, "java/security/KeyStore$TrustedCertificateEntry");
+    g_TrustedCertificateEntryGetTrustedCertificate =    GetMethod(env, false, g_TrustedCertificateEntryClass, "getTrustedCertificate", "()Ljava/security/cert/Certificate;");
+
     g_SignatureClass =                 GetClassGRef(env, "java/security/Signature");
     g_SignatureGetInstance =           GetMethod(env, true, g_SignatureClass, "getInstance", "(Ljava/lang/String;)Ljava/security/Signature;");
     g_SignatureInitSign =              GetMethod(env, false, g_SignatureClass, "initSign", "(Ljava/security/PrivateKey;)V");
@@ -665,6 +721,10 @@ JNI_OnLoad(JavaVM *vm, void *reserved)
     g_DateClass =   GetClassGRef(env, "java/util/Date");
     g_DateGetTime = GetMethod(env, false, g_DateClass, "getTime", "()J");
 
+    g_Enumeration =                 GetClassGRef(env, "java/util/Enumeration");
+    g_EnumerationHasMoreElements =  GetMethod(env, false, g_Enumeration, "hasMoreElements", "()Z");
+    g_EnumerationNextElement =      GetMethod(env, false, g_Enumeration, "nextElement", "()Ljava/lang/Object;");
+
     g_IteratorClass =   GetClassGRef(env, "java/util/Iterator");
     g_IteratorHasNext = GetMethod(env, false, g_IteratorClass, "hasNext", "()Z");
     g_IteratorNext =    GetMethod(env, false, g_IteratorClass, "next", "()Ljava/lang/Object;");
index 0b43632..d662ea5 100644 (file)
@@ -109,9 +109,16 @@ extern jmethodID g_CertPathGetEncoded;
 
 // java/security/cert/X509Certificate
 extern jclass    g_X509CertClass;
+extern jmethodID g_X509CertEquals;
 extern jmethodID g_X509CertGetEncoded;
 extern jmethodID g_X509CertGetPublicKey;
 
+// java/security/interfaces/DSAKey
+extern jclass    g_DSAKeyClass;
+
+// java/security/interfaces/ECKey
+extern jclass    g_ECKeyClass;
+
 // java/security/interfaces/RSAKey
 extern jclass    g_RSAKeyClass;
 extern jmethodID g_RSAKeyGetModulus;
@@ -133,6 +140,27 @@ extern jmethodID g_keyPairGenInitializeMethod;
 extern jmethodID g_keyPairGenInitializeWithParamsMethod;
 extern jmethodID g_keyPairGenGenKeyPairMethod;
 
+// java/security/KeyStore
+extern jclass    g_KeyStoreClass;
+extern jmethodID g_KeyStoreGetInstance;
+extern jmethodID g_KeyStoreAliases;
+extern jmethodID g_KeyStoreContainsAlias;
+extern jmethodID g_KeyStoreDeleteEntry;
+extern jmethodID g_KeyStoreGetCertificate;
+extern jmethodID g_KeyStoreGetEntry;
+extern jmethodID g_KeyStoreLoad;
+extern jmethodID g_KeyStoreSetCertificateEntry;
+extern jmethodID g_KeyStoreSetKeyEntry;
+
+// java/security/KeyStore$PrivateKeyEntry
+extern jclass    g_PrivateKeyEntryClass;
+extern jmethodID g_PrivateKeyEntryGetCertificate;
+extern jmethodID g_PrivateKeyEntryGetPrivateKey;
+
+// java/security/KeyStore$TrustedCertificateEntry
+extern jclass    g_TrustedCertificateEntryClass;
+extern jmethodID g_TrustedCertificateEntryGetTrustedCertificate;
+
 // java/security/Signature
 extern jclass    g_SignatureClass;
 extern jmethodID g_SignatureGetInstance;
@@ -263,6 +291,11 @@ extern jmethodID g_CollectionSize;
 extern jclass    g_DateClass;
 extern jmethodID g_DateGetTime;
 
+// java/util/Enumeration
+extern jclass    g_Enumeration;
+extern jmethodID g_EnumerationHasMoreElements;
+extern jmethodID g_EnumerationNextElement;
+
 // java/util/Iterator
 extern jclass    g_IteratorClass;
 extern jmethodID g_IteratorHasNext;
index 68091b7..0fb5296 100644 (file)
@@ -162,7 +162,7 @@ PALEXPORT RSA* AndroidCryptoNative_DecodeRsaSubjectPublicKeyInfo(uint8_t* buf, i
         return FAIL;
     }
 
-    RSA* rsa = AndroidCryptoNative_NewRsaFromPublicKey(env, publicKey);
+    RSA* rsa = AndroidCryptoNative_NewRsaFromKeys(env, publicKey, NULL /*privateKey*/);
     (*env)->DeleteLocalRef(env, publicKey);
 
     return rsa;
@@ -385,15 +385,16 @@ PALEXPORT int32_t AndroidCryptoNative_SetRsaParameters(RSA* rsa,
     return CheckJNIExceptions(env) ? FAIL : SUCCESS;
 }
 
-RSA* AndroidCryptoNative_NewRsaFromPublicKey(JNIEnv* env, jobject /*RSAPublicKey*/ key)
+RSA* AndroidCryptoNative_NewRsaFromKeys(JNIEnv* env, jobject /*RSAPublicKey*/ publicKey, jobject /*RSAPrivateKey*/ privateKey)
 {
-    if (!(*env)->IsInstanceOf(env, key, g_RSAPublicKeyClass))
+    if (!(*env)->IsInstanceOf(env, publicKey, g_RSAPublicKeyClass))
         return NULL;
 
-    jobject modulus = (*env)->CallObjectMethod(env, key, g_RSAKeyGetModulus);
+    jobject modulus = (*env)->CallObjectMethod(env, publicKey, g_RSAKeyGetModulus);
 
     RSA* ret = AndroidCryptoNative_RsaCreate();
-    ret->publicKey = AddGRef(env, key);
+    ret->publicKey = AddGRef(env, publicKey);
+    ret->privateKey = AddGRef(env, privateKey);
     ret->keyWidthInBits = AndroidCryptoNative_GetBigNumBytes(modulus) * 8;
 
     (*env)->DeleteLocalRef(env, modulus);
index 2bba552..5cc99ec 100644 (file)
@@ -41,4 +41,5 @@ PALEXPORT int32_t AndroidCryptoNative_SetRsaParameters(RSA* rsa,
     uint8_t* p,    int32_t pLength,    uint8_t* dmp1, int32_t dmp1Length, uint8_t* q, int32_t qLength,
     uint8_t* dmq1, int32_t dmq1Length, uint8_t* iqmp, int32_t iqmpLength);
 
+RSA* AndroidCryptoNative_NewRsaFromKeys(JNIEnv* env, jobject /*RSAPublicKey*/ publicKey, jobject /*RSAPrivateKey*/ privateKey);
 RSA* AndroidCryptoNative_NewRsaFromPublicKey(JNIEnv* env, jobject /*RSAPublicKey*/ key);
index b544040..f244e04 100644 (file)
@@ -258,13 +258,13 @@ void* AndroidCryptoNative_X509PublicKey(jobject /*X509Certificate*/ cert, PAL_Ke
     switch (algorithm)
     {
         case PAL_EC:
-            keyHandle = AndroidCryptoNative_NewEcKeyFromPublicKey(env, key);
+            keyHandle = AndroidCryptoNative_NewEcKeyFromKeys(env, key, NULL /*privateKey*/);
             break;
         case PAL_DSA:
-            keyHandle = AndroidCryptoNative_CreateKeyPair(env, key, NULL);
+            keyHandle = AndroidCryptoNative_CreateKeyPair(env, key, NULL /*privateKey*/);
             break;
         case PAL_RSA:
-            keyHandle = AndroidCryptoNative_NewRsaFromPublicKey(env, key);
+            keyHandle = AndroidCryptoNative_NewRsaFromKeys(env, key, NULL /*privateKey*/);
             break;
         default:
             keyHandle = NULL;
index 476d0de..e00e52f 100644 (file)
@@ -56,8 +56,10 @@ enum
     PAL_DSA = 0,
     PAL_EC = 1,
     PAL_RSA = 2,
+
+    PAL_UnknownAlgorithm = -1,
 };
-typedef uint32_t PAL_KeyAlgorithm;
+typedef int32_t PAL_KeyAlgorithm;
 
 /*
 Gets an opaque handle for a certificate's public key
diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_x509store.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_x509store.c
new file mode 100644 (file)
index 0000000..3f553cb
--- /dev/null
@@ -0,0 +1,465 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#include "pal_x509store.h"
+#include "pal_eckey.h"
+#include "pal_misc.h"
+#include "pal_rsa.h"
+
+#include <assert.h>
+#include <stdbool.h>
+#include <string.h>
+
+#define INIT_LOCALS(name, ...) \
+    enum { __VA_ARGS__, count_##name }; \
+    jobject name[count_##name] = { 0 } \
+
+#define RELEASE_LOCALS(name, env) \
+do { \
+    for (int i_##name = 0; i_##name < count_##name; ++i_##name) \
+    { \
+        jobject local = name[i_##name]; \
+        if (local != NULL) \
+            (*env)->DeleteLocalRef(env, local); \
+    } \
+} while (0)
+
+typedef enum
+{
+    EntryFlags_None = 0,
+    EntryFlags_HasCertificate = 1,
+    EntryFlags_HasPrivateKey = 2,
+    EntryFlags_MatchesCertificate = 4,
+} EntryFlags;
+
+// Returns whether or not the store contains the specified alias
+// If the entry exists, the flags parameter is set based on the contents of the entry
+static bool ContainsEntryForAlias(
+    JNIEnv* env, jobject /*KeyStore*/ store, jobject /*X509Certificate*/ cert, jstring alias, EntryFlags* flags)
+{
+    bool ret = false;
+    EntryFlags flagsLocal = EntryFlags_None;
+
+    INIT_LOCALS(loc, entry, existingCert);
+
+    bool containsAlias = (*env)->CallBooleanMethod(env, store, g_KeyStoreContainsAlias, alias);
+    if (!containsAlias)
+        goto cleanup;
+
+    ret = true;
+
+    // KeyStore.Entry entry = store.getEntry(alias, null);
+    // if (entry instanceof KeyStore.PrivateKeyEntry) {
+    //     existingCert = ((KeyStore.PrivateKeyEntry)entry).getCertificate();
+    // } else if (entry instanceof KeyStore.TrustedCertificateEntry) {
+    //     existingCert = ((KeyStore.TrustedCertificateEntry)entry).getTrustedCertificate();
+    // }
+    loc[entry] = (*env)->CallObjectMethod(env, store, g_KeyStoreGetEntry, alias, NULL);
+    ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
+    if ((*env)->IsInstanceOf(env, loc[entry], g_PrivateKeyEntryClass))
+    {
+        // Private key entries always have a certificate
+        flagsLocal |= EntryFlags_HasCertificate;
+        flagsLocal |= EntryFlags_HasPrivateKey;
+        loc[existingCert] = (*env)->CallObjectMethod(env, loc[entry], g_PrivateKeyEntryGetCertificate);
+    }
+    else if ((*env)->IsInstanceOf(env, loc[entry], g_TrustedCertificateEntryClass))
+    {
+        flagsLocal |= EntryFlags_HasCertificate;
+        loc[existingCert] = (*env)->CallObjectMethod(env, loc[entry], g_TrustedCertificateEntryGetTrustedCertificate);
+    }
+    else
+    {
+        // Entry for alias exists, but doesn't represent a certificate or private key + certificate
+        goto cleanup;
+    }
+
+    assert(loc[existingCert] != NULL);
+    if ((*env)->CallBooleanMethod(env, cert, g_X509CertEquals, loc[existingCert]))
+    {
+        flagsLocal |= EntryFlags_MatchesCertificate;
+    }
+
+cleanup:
+    RELEASE_LOCALS(loc, env);
+    *flags = flagsLocal;
+    return ret;
+}
+
+static bool ContainsMatchingCertificateForAlias(JNIEnv* env,
+                                                jobject /*KeyStore*/ store,
+                                                jobject /*X509Certificate*/ cert,
+                                                jstring alias)
+{
+    EntryFlags flags;
+    if (!ContainsEntryForAlias(env, store, cert, alias, &flags))
+        return false;
+
+    EntryFlags matchesFlags = EntryFlags_HasCertificate & EntryFlags_MatchesCertificate;
+    return (flags & matchesFlags) == matchesFlags;
+}
+
+int32_t AndroidCryptoNative_X509StoreAddCertificate(jobject /*KeyStore*/ store,
+                                                    jobject /*X509Certificate*/ cert,
+                                                    const char* hashString)
+{
+    assert(store != NULL);
+    assert(cert != NULL);
+
+    JNIEnv* env = GetJNIEnv();
+
+    jstring alias = JSTRING(hashString);
+    EntryFlags flags;
+    if (ContainsEntryForAlias(env, store, cert, alias, &flags))
+    {
+        EntryFlags matchesFlags = EntryFlags_HasCertificate & EntryFlags_MatchesCertificate;
+        if ((flags & matchesFlags) != matchesFlags)
+        {
+            LOG_ERROR("Store already contains alias with entry that does not match the expected certificate");
+            return FAIL;
+        }
+
+        // Certificate is already in store - nothing to do
+        LOG_DEBUG("Store already contains certificate");
+        return SUCCESS;
+    }
+
+    // store.setCertificateEntry(alias, cert);
+    (*env)->CallVoidMethod(env, store, g_KeyStoreSetCertificateEntry, alias, cert);
+    (*env)->DeleteLocalRef(env, alias);
+
+    return CheckJNIExceptions(env) ? FAIL : SUCCESS;
+}
+
+int32_t AndroidCryptoNative_X509StoreAddCertificateWithPrivateKey(jobject /*KeyStore*/ store,
+                                                                  jobject /*X509Certificate*/ cert,
+                                                                  void* key,
+                                                                  PAL_KeyAlgorithm algorithm,
+                                                                  const char* hashString)
+{
+    assert(store != NULL);
+    assert(cert != NULL);
+    assert(key != NULL);
+
+    int32_t ret = FAIL;
+    JNIEnv* env = GetJNIEnv();
+
+    INIT_LOCALS(loc, alias, certs);
+    jobject privateKey = NULL;
+
+    loc[alias] = JSTRING(hashString);
+
+    EntryFlags flags;
+    if (ContainsEntryForAlias(env, store, cert, loc[alias], &flags))
+    {
+        EntryFlags matchesFlags = EntryFlags_HasCertificate & EntryFlags_MatchesCertificate;
+        if ((flags & matchesFlags) != matchesFlags)
+        {
+            LOG_ERROR("Store already contains alias with entry that does not match the expected certificate");
+            return FAIL;
+        }
+
+        if ((flags & EntryFlags_HasPrivateKey) == EntryFlags_HasPrivateKey)
+        {
+            // Certificate with private key is already in store - nothing to do
+            LOG_DEBUG("Store already contains certificate with private key");
+            return SUCCESS;
+        }
+
+        // Delete existing entry. We will replace the existing cert with the cert + private key.
+        // store.deleteEntry(alias);
+        (*env)->CallVoidMethod(env, store, g_KeyStoreDeleteEntry, alias);
+    }
+
+    bool releasePrivateKey = true;
+    switch (algorithm)
+    {
+        case PAL_EC:
+        {
+            EC_KEY* ec = (EC_KEY*)key;
+            privateKey = (*env)->CallObjectMethod(env, ec->keyPair, g_keyPairGetPrivateMethod);
+            break;
+        }
+        case PAL_DSA:
+        {
+            // key is a KeyPair jobject
+            privateKey = (*env)->CallObjectMethod(env, key, g_keyPairGetPrivateMethod);
+            break;
+        }
+        case PAL_RSA:
+        {
+            RSA* rsa = (RSA*)key;
+            privateKey = rsa->privateKey;
+            releasePrivateKey = false; // Private key is a global ref stored directly on RSA handle
+            break;
+        }
+        default:
+        {
+            releasePrivateKey = false;
+            LOG_ERROR("Unknown algorithm for private key");
+            goto cleanup;
+        }
+    }
+
+    // X509Certificate[] certs = new X509Certificate[] { cert };
+    // store.setKeyEntry(alias, privateKey, null, certs);
+    loc[certs] = (*env)->NewObjectArray(env, 1, g_X509CertClass, cert);
+    (*env)->CallVoidMethod(env, store, g_KeyStoreSetKeyEntry, loc[alias], privateKey, NULL, loc[certs]);
+    ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
+
+    ret = SUCCESS;
+
+cleanup:
+    RELEASE_LOCALS(loc, env);
+    if (releasePrivateKey)
+    {
+        (*env)->DeleteLocalRef(env, privateKey);
+    }
+
+    return ret;
+}
+
+bool AndroidCryptoNative_X509StoreContainsCertificate(jobject /*KeyStore*/ store,
+                                                      jobject /*X509Certificate*/ cert,
+                                                      const char* hashString)
+{
+    assert(store != NULL);
+    assert(cert != NULL);
+
+    JNIEnv* env = GetJNIEnv();
+    jstring alias = JSTRING(hashString);
+
+    bool containsCert = ContainsMatchingCertificateForAlias(env, store, cert, alias);
+    (*env)->DeleteLocalRef(env, alias);
+    return containsCert;
+}
+
+static void* HandleFromKeys(JNIEnv* env,
+                            jobject /*PublicKey*/ publicKey,
+                            jobject /*PrivateKey*/ privateKey,
+                            PAL_KeyAlgorithm* algorithm)
+{
+    if ((*env)->IsInstanceOf(env, privateKey, g_DSAKeyClass))
+    {
+        *algorithm = PAL_DSA;
+        return AndroidCryptoNative_CreateKeyPair(env, publicKey, privateKey);
+    }
+    else if ((*env)->IsInstanceOf(env, privateKey, g_ECKeyClass))
+    {
+        *algorithm = PAL_EC;
+        return AndroidCryptoNative_NewEcKeyFromKeys(env, publicKey, privateKey);
+    }
+    else if ((*env)->IsInstanceOf(env, privateKey, g_RSAKeyClass))
+    {
+        *algorithm = PAL_RSA;
+        return AndroidCryptoNative_NewRsaFromKeys(env, publicKey, privateKey);
+    }
+
+    LOG_INFO("Ignoring unknown privake key type");
+    *algorithm = PAL_UnknownAlgorithm;
+    return NULL;
+}
+
+static int32_t
+EnumerateCertificates(JNIEnv* env, jobject /*KeyStore*/ store, EnumCertificatesCallback cb, void* context)
+{
+    int32_t ret = FAIL;
+
+    // Enumeration<String> aliases = store.aliases();
+    jobject aliases = (*env)->CallObjectMethod(env, store, g_KeyStoreAliases);
+    ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
+
+    // while (aliases.hasMoreElements()) {
+    //     String alias = aliases.nextElement();
+    //     KeyStore.Entry entry = store.getEntry(alias);
+    //     if (entry instanceof KeyStore.PrivateKeyEntry) {
+    //         ...
+    //     } else if (entry instanceof KeyStore.TrustedCertificateEntry) {
+    //         ...
+    //     }
+    // }
+    jboolean hasNext = (*env)->CallBooleanMethod(env, aliases, g_EnumerationHasMoreElements);
+    while (hasNext)
+    {
+        INIT_LOCALS(loc, alias, entry, cert, publicKey, privateKey);
+
+        loc[alias] = (*env)->CallObjectMethod(env, aliases, g_EnumerationNextElement);
+        ON_EXCEPTION_PRINT_AND_GOTO(loop_cleanup);
+
+        loc[entry] = (*env)->CallObjectMethod(env, store, g_KeyStoreGetEntry, loc[alias], NULL);
+        ON_EXCEPTION_PRINT_AND_GOTO(loop_cleanup);
+
+        if ((*env)->IsInstanceOf(env, loc[entry], g_PrivateKeyEntryClass))
+        {
+            // Certificate cert = entry.getCertificate();
+            // Public publicKey = cert.getPublicKey();
+            // PrivateKey privateKey = entry.getPrivateKey();
+            loc[cert] = (*env)->CallObjectMethod(env, loc[entry], g_PrivateKeyEntryGetCertificate);
+            loc[publicKey] = (*env)->CallObjectMethod(env, loc[cert], g_X509CertGetPublicKey);
+            loc[privateKey] = (*env)->CallObjectMethod(env, loc[entry], g_PrivateKeyEntryGetPrivateKey);
+
+            PAL_KeyAlgorithm keyAlgorithm = PAL_UnknownAlgorithm;
+            void* keyHandle = HandleFromKeys(env, loc[publicKey], loc[privateKey], &keyAlgorithm);
+
+            // Private key entries always have a certificate.
+            // For key algorithms we recognize, the certificate and private key handle are given to the callback.
+            // For key algorithms we do not recognize, only the certificate will be given to the callback.
+            cb(AddGRef(env, loc[cert]), keyHandle, keyAlgorithm, context);
+        }
+        else if ((*env)->IsInstanceOf(env, loc[entry], g_TrustedCertificateEntryClass))
+        {
+            // Certificate cert = entry.getTrustedCertificate();
+            loc[cert] = (*env)->CallObjectMethod(env, loc[entry], g_TrustedCertificateEntryGetTrustedCertificate);
+            cb(AddGRef(env, loc[cert]), NULL /*privateKey*/, PAL_UnknownAlgorithm, context);
+        }
+
+    loop_cleanup:
+        RELEASE_LOCALS(loc, env);
+
+        hasNext = (*env)->CallBooleanMethod(env, aliases, g_EnumerationHasMoreElements);
+    }
+
+    ret = SUCCESS;
+
+cleanup:
+    (*env)->DeleteLocalRef(env, aliases);
+    return ret;
+}
+
+int32_t AndroidCryptoNative_X509StoreEnumerateCertificates(jobject /*KeyStore*/ store,
+                                                           EnumCertificatesCallback cb,
+                                                           void* context)
+{
+    assert(store != NULL);
+    assert(cb != NULL);
+
+    JNIEnv* env = GetJNIEnv();
+    return EnumerateCertificates(env, store, cb, context);
+}
+
+static bool SystemAliasFilter(JNIEnv* env, jstring alias)
+{
+    const char systemPrefix[] = "system:";
+    size_t prefixLen = (sizeof(systemPrefix) - 1) / sizeof(char);
+
+    const char* aliasPtr = (*env)->GetStringUTFChars(env, alias, NULL);
+    bool isSystem = (strncmp(aliasPtr, systemPrefix, prefixLen) == 0);
+    (*env)->ReleaseStringUTFChars(env, alias, aliasPtr);
+    return isSystem;
+}
+
+typedef bool (*FilterAliasFunction)(JNIEnv* env, jstring alias);
+static int32_t EnumerateTrustedCertificates(
+    JNIEnv* env, jobject /*KeyStore*/ store, bool systemOnly, EnumTrustedCertificatesCallback cb, void* context)
+{
+    int32_t ret = FAIL;
+
+    // Filter to only system certificates if necessary
+    // System certificates are included for 'current user' (matches Windows)
+    FilterAliasFunction filter = systemOnly ? &SystemAliasFilter : NULL;
+
+    // Enumeration<String> aliases = store.aliases();
+    jobject aliases = (*env)->CallObjectMethod(env, store, g_KeyStoreAliases);
+    ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
+
+    // while (aliases.hasMoreElements()) {
+    //     String alias = aliases.nextElement();
+    //     X509Certificate cert = (X509Certificate)store.getCertificate(alias);
+    //     if (cert != null) {
+    //         cb(cert, context);
+    //     }
+    // }
+    jboolean hasNext = (*env)->CallBooleanMethod(env, aliases, g_EnumerationHasMoreElements);
+    while (hasNext)
+    {
+        jstring alias = (*env)->CallObjectMethod(env, aliases, g_EnumerationNextElement);
+        ON_EXCEPTION_PRINT_AND_GOTO(loop_cleanup);
+
+        if (filter == NULL || filter(env, alias))
+        {
+            jobject cert = (*env)->CallObjectMethod(env, store, g_KeyStoreGetCertificate, alias);
+            if (cert != NULL && !CheckJNIExceptions(env))
+            {
+                cert = ToGRef(env, cert);
+                cb(cert, context);
+            }
+        }
+
+        hasNext = (*env)->CallBooleanMethod(env, aliases, g_EnumerationHasMoreElements);
+
+    loop_cleanup:
+        (*env)->DeleteLocalRef(env, alias);
+    }
+
+    ret = SUCCESS;
+
+cleanup:
+    (*env)->DeleteLocalRef(env, aliases);
+    return ret;
+}
+
+int32_t AndroidCryptoNative_X509StoreEnumerateTrustedCertificates(bool systemOnly,
+                                                                  EnumTrustedCertificatesCallback cb,
+                                                                  void* context)
+{
+    assert(cb != NULL);
+    JNIEnv* env = GetJNIEnv();
+
+    int32_t ret = FAIL;
+    INIT_LOCALS(loc, storeType, store);
+
+    // KeyStore store = KeyStore.getInstance("AndroidCAStore");
+    // store.load(null, null);
+    loc[storeType] = JSTRING("AndroidCAStore");
+    loc[store] = (*env)->CallStaticObjectMethod(env, g_KeyStoreClass, g_KeyStoreGetInstance, loc[storeType]);
+    ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
+    (*env)->CallVoidMethod(env, loc[store], g_KeyStoreLoad, NULL, NULL);
+    ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
+
+    ret = EnumerateTrustedCertificates(env, loc[store], systemOnly, cb, context);
+
+cleanup:
+    RELEASE_LOCALS(loc, env);
+    return ret;
+}
+
+jobject /*KeyStore*/ AndroidCryptoNative_X509StoreOpenDefault(void)
+{
+    JNIEnv* env = GetJNIEnv();
+    jobject ret = NULL;
+
+    // KeyStore store = KeyStore.getInstance("AndroidKeyStore");
+    // store.load(null, null);
+    jstring storeType = JSTRING("AndroidKeyStore");
+    jobject store = (*env)->CallStaticObjectMethod(env, g_KeyStoreClass, g_KeyStoreGetInstance, storeType);
+    ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
+    (*env)->CallVoidMethod(env, store, g_KeyStoreLoad, NULL, NULL);
+    ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
+    ret = ToGRef(env, store);
+
+cleanup:
+    (*env)->DeleteLocalRef(env, storeType);
+    return ret;
+}
+
+int32_t AndroidCryptoNative_X509StoreRemoveCertificate(jobject /*KeyStore*/ store,
+                                                       jobject /*X509Certificate*/ cert,
+                                                       const char* hashString)
+{
+    assert(store != NULL);
+
+    JNIEnv* env = GetJNIEnv();
+
+    jstring alias = JSTRING(hashString);
+    if (!ContainsMatchingCertificateForAlias(env, store, cert, alias))
+    {
+        // Certificate is not in store - nothing to do
+        return SUCCESS;
+    }
+
+    // store.deleteEntry(alias);
+    (*env)->CallVoidMethod(env, store, g_KeyStoreDeleteEntry, alias);
+
+    (*env)->DeleteLocalRef(env, alias);
+    return CheckJNIExceptions(env) ? FAIL : SUCCESS;
+}
diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_x509store.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_x509store.h
new file mode 100644 (file)
index 0000000..1702f19
--- /dev/null
@@ -0,0 +1,72 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#include "pal_jni.h"
+#include "pal_x509.h"
+
+/*
+Add a certificate to the specified key store
+
+If the certificate is already in the store, this function simply returns success.
+Returns 1 on success, 0 otherwise.
+*/
+PALEXPORT int32_t AndroidCryptoNative_X509StoreAddCertificate(jobject /*KeyStore*/ store,
+                                                              jobject /*X509Certificate*/ cert,
+                                                              const char* hashString);
+
+/*
+Add a certificate with a private key to the specified key store
+
+If the certificate is already in the store, it is replaced. This means that a certificate
+without a private key will be replaced with one with a private key.
+Returns 1 on success, 0 otherwise.
+*/
+PALEXPORT int32_t AndroidCryptoNative_X509StoreAddCertificateWithPrivateKey(jobject /*KeyStore*/ store,
+                                                                            jobject /*X509Certificate*/ cert,
+                                                                            void* key,
+                                                                            PAL_KeyAlgorithm algorithm,
+                                                                            const char* hashString);
+
+/*
+Get whether or not a certificate is contained in the specified key store
+*/
+PALEXPORT bool AndroidCryptoNative_X509StoreContainsCertificate(jobject /*KeyStore*/ store,
+                                                                jobject /*X509Certificate*/ cert,
+                                                                const char* hashString);
+
+/*
+Enumerate certificates for the specified key store
+The certificate and key passed to the callback will already be a global jobject reference.
+*/
+typedef void (*EnumCertificatesCallback)(jobject /*X509Certificate*/ cert,
+                                         void* privateKey,
+                                         PAL_KeyAlgorithm algorithm,
+                                         void* context);
+PALEXPORT int32_t AndroidCryptoNative_X509StoreEnumerateCertificates(jobject /*KeyStore*/ store,
+                                                                     EnumCertificatesCallback cb,
+                                                                     void* context);
+
+/*
+Enumerate trusted certificates
+The certificate passed to the callback will already be a global jobject reference.
+
+Returns 1 on success, 0 otherwise.
+*/
+typedef void (*EnumTrustedCertificatesCallback)(jobject /*X509Certificate*/ cert, void* context);
+PALEXPORT int32_t AndroidCryptoNative_X509StoreEnumerateTrustedCertificates(bool isSystem,
+                                                                            EnumTrustedCertificatesCallback cb,
+                                                                            void* context);
+/*
+Open the default key store
+*/
+PALEXPORT jobject /*KeyStore*/ AndroidCryptoNative_X509StoreOpenDefault(void);
+
+/*
+Remove a certificate from the specified key store
+
+If the certificate is not in the store, this function returns success.
+Returns 1 on success, 0 otherwise.
+*/
+PALEXPORT int32_t AndroidCryptoNative_X509StoreRemoveCertificate(jobject /*KeyStore*/ store,
+                                                                 jobject /*X509Certificate*/ cert,
+                                                                 const char* hashString);
index 740b878..658c2da 100644 (file)
@@ -121,12 +121,12 @@ namespace Internal.Cryptography.Pal
             }
         }
 
-        private AndroidCertificatePal(SafeX509Handle handle)
+        internal AndroidCertificatePal(SafeX509Handle handle)
         {
             _cert = handle;
         }
 
-        private AndroidCertificatePal(SafeX509Handle handle, SafeKeyHandle privateKey)
+        internal AndroidCertificatePal(SafeX509Handle handle, SafeKeyHandle privateKey)
         {
             _cert = handle;
             _privateKey = privateKey;
diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Android/StorePal.AndroidKeyStore.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Android/StorePal.AndroidKeyStore.cs
new file mode 100644 (file)
index 0000000..898d79c
--- /dev/null
@@ -0,0 +1,159 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using Microsoft.Win32.SafeHandles;
+
+namespace Internal.Cryptography.Pal
+{
+    internal sealed partial class StorePal
+    {
+        private sealed class AndroidKeyStore : IStorePal
+        {
+            private readonly bool _readOnly;
+            private readonly SafeX509StoreHandle _keyStoreHandle;
+
+            public static AndroidKeyStore OpenDefault(OpenFlags openFlags)
+            {
+                SafeX509StoreHandle store = Interop.AndroidCrypto.X509StoreOpenDefault();
+                if (store.IsInvalid)
+                {
+                    store.Dispose();
+                    throw new CryptographicException();
+                }
+
+                return new AndroidKeyStore(store, openFlags);
+            }
+
+            private AndroidKeyStore(SafeX509StoreHandle keyStoreHandle, OpenFlags openFlags)
+            {
+                _keyStoreHandle = keyStoreHandle;
+                _readOnly = (openFlags & (OpenFlags.ReadWrite | OpenFlags.MaxAllowed)) == 0;
+            }
+
+            public SafeHandle SafeHandle => _keyStoreHandle;
+
+            public void Dispose()
+            {
+                _keyStoreHandle.Dispose();
+            }
+
+            public void Add(ICertificatePal cert)
+            {
+                if (_readOnly)
+                    throw new CryptographicException(SR.Cryptography_X509_StoreReadOnly);
+
+                AndroidCertificatePal certPal = (AndroidCertificatePal)cert;
+                string hashString = GetCertificateHashString(cert);
+
+                bool success;
+                if (certPal.HasPrivateKey)
+                {
+                    Interop.AndroidCrypto.PAL_KeyAlgorithm algorithm = certPal.PrivateKeyHandle switch
+                    {
+                        // The AndroidKeyStore doesn't support adding DSA private key entries in newer versions (API 23+)
+                        // Our minimum supported version (API 21) does support it, but for simplicity, we simply block adding
+                        // certificates with DSA private keys on all versions instead of trying to support it on two versions.
+                        SafeDsaHandle _ => throw new PlatformNotSupportedException(SR.Cryptography_X509_StoreDSAPrivateKeyNotSupported),
+                        SafeEcKeyHandle _ => Interop.AndroidCrypto.PAL_KeyAlgorithm.EC,
+                        SafeRsaHandle _ => Interop.AndroidCrypto.PAL_KeyAlgorithm.RSA,
+                        _ => throw new NotSupportedException(SR.NotSupported_KeyAlgorithm)
+                    };
+
+                    success = Interop.AndroidCrypto.X509StoreAddCertificateWithPrivateKey(_keyStoreHandle, certPal.SafeHandle, certPal.PrivateKeyHandle, algorithm, hashString);
+                }
+                else
+                {
+                    success = Interop.AndroidCrypto.X509StoreAddCertificate(_keyStoreHandle, certPal.SafeHandle, hashString);
+                }
+
+                if (!success)
+                    throw new CryptographicException(SR.Cryptography_X509_StoreAddFailure);
+            }
+
+            public void Remove(ICertificatePal cert)
+            {
+                string hashString = GetCertificateHashString(cert);
+                AndroidCertificatePal certPal = (AndroidCertificatePal)cert;
+                if (_readOnly)
+                {
+                    bool containsCert = Interop.AndroidCrypto.X509StoreContainsCertificate(_keyStoreHandle, certPal.SafeHandle, hashString);
+                    if (containsCert)
+                        throw new CryptographicException(SR.Cryptography_X509_StoreReadOnly);
+
+                    // Removing a non-existent certificate is not an error
+                    return;
+                }
+
+                bool success = Interop.AndroidCrypto.X509StoreRemoveCertificate(_keyStoreHandle, certPal.SafeHandle, hashString);
+                if (!success)
+                    throw new CryptographicException(SR.Cryptography_X509_StoreRemoveFailure);
+            }
+
+            public void CloneTo(X509Certificate2Collection collection)
+            {
+                EnumCertificatesContext context = default;
+                context.Results = new HashSet<X509Certificate2>();
+                unsafe
+                {
+                    bool success = Interop.AndroidCrypto.X509StoreEnumerateCertificates(
+                        _keyStoreHandle,
+                        &EnumCertificatesCallback,
+                        Unsafe.AsPointer(ref context));
+                    if (!success)
+                    {
+                        throw new CryptographicException(SR.Cryptography_X509_StoreEnumerateFailure);
+                    }
+                }
+
+                foreach (X509Certificate2 cert in context.Results)
+                {
+                    collection.Add(cert);
+                }
+            }
+
+            private static string GetCertificateHashString(ICertificatePal certPal)
+            {
+                return X509Certificate.GetCertHashString(HashAlgorithmName.SHA256, certPal);
+            }
+
+            private struct EnumCertificatesContext
+            {
+                public HashSet<X509Certificate2> Results;
+            }
+
+            [UnmanagedCallersOnly]
+            private static unsafe void EnumCertificatesCallback(void* certPtr, void* privateKeyPtr, Interop.AndroidCrypto.PAL_KeyAlgorithm privateKeyAlgorithm, void* context)
+            {
+                ref EnumCertificatesContext callbackContext = ref Unsafe.As<byte, EnumCertificatesContext>(ref *(byte*)context);
+
+                AndroidCertificatePal certPal;
+                var handle = new SafeX509Handle((IntPtr)certPtr);
+                if (privateKeyPtr != null)
+                {
+                    SafeKeyHandle privateKey = privateKeyAlgorithm switch
+                    {
+                        Interop.AndroidCrypto.PAL_KeyAlgorithm.DSA => new SafeDsaHandle((IntPtr)privateKeyPtr),
+                        Interop.AndroidCrypto.PAL_KeyAlgorithm.EC => new SafeEcKeyHandle((IntPtr)privateKeyPtr),
+                        Interop.AndroidCrypto.PAL_KeyAlgorithm.RSA => new SafeRsaHandle((IntPtr)privateKeyPtr),
+                        _ => throw new NotSupportedException(SR.NotSupported_KeyAlgorithm)
+                    };
+                    certPal = new AndroidCertificatePal(handle, privateKey);
+                }
+                else
+                {
+                    certPal = new AndroidCertificatePal(handle);
+                }
+
+                var cert = new X509Certificate2(certPal);
+                if (!callbackContext.Results.Add(cert))
+                    cert.Dispose();
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Android/StorePal.TrustedStore.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Android/StorePal.TrustedStore.cs
new file mode 100644 (file)
index 0000000..6998610
--- /dev/null
@@ -0,0 +1,81 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using Microsoft.Win32.SafeHandles;
+
+namespace Internal.Cryptography.Pal
+{
+    internal sealed partial class StorePal
+    {
+        private sealed class TrustedStore : IStorePal
+        {
+            private readonly StoreLocation _location;
+
+            internal TrustedStore(StoreLocation location)
+            {
+                _location = location;
+            }
+
+            public SafeHandle? SafeHandle => null;
+
+            public void Dispose()
+            {
+            }
+
+            public void Add(ICertificatePal cert)
+            {
+                throw new CryptographicException(SR.Cryptography_X509_StoreReadOnly);
+            }
+
+            public void Remove(ICertificatePal cert)
+            {
+                throw new CryptographicException(SR.Cryptography_X509_StoreReadOnly);
+            }
+
+            public void CloneTo(X509Certificate2Collection collection)
+            {
+                EnumCertificatesContext context = default;
+                context.Results = new HashSet<X509Certificate2>();
+
+                bool systemOnly = _location == StoreLocation.LocalMachine;
+                unsafe
+                {
+                    bool success = Interop.AndroidCrypto.X509StoreEnumerateTrustedCertificates(
+                        (byte)(systemOnly ? 1 : 0),
+                        &EnumCertificatesCallback,
+                        Unsafe.AsPointer(ref context));
+                    if (!success)
+                    {
+                        throw new CryptographicException(SR.Cryptography_X509_StoreEnumerateFailure);
+                    }
+                }
+
+                foreach (X509Certificate2 cert in context.Results)
+                {
+                    collection.Add(cert);
+                }
+            }
+
+            private struct EnumCertificatesContext
+            {
+                public HashSet<X509Certificate2> Results;
+            }
+
+            [UnmanagedCallersOnly]
+            private static unsafe void EnumCertificatesCallback(void* certPtr, void* context)
+            {
+                ref EnumCertificatesContext callbackContext = ref Unsafe.As<byte, EnumCertificatesContext>(ref *(byte*)context);
+                var handle = new SafeX509Handle((IntPtr)certPtr);
+                var cert = new X509Certificate2(new AndroidCertificatePal(handle));
+                if (!callbackContext.Results.Add(cert))
+                    cert.Dispose();
+            }
+        }
+    }
+}
index 3fe9028..09ec3ce 100644 (file)
@@ -53,7 +53,59 @@ namespace Internal.Cryptography.Pal
 
         public static IStorePal FromSystemStore(string storeName, StoreLocation storeLocation, OpenFlags openFlags)
         {
-            throw new NotImplementedException(nameof(FromSystemStore));
+            bool isReadWrite = (openFlags & OpenFlags.ReadWrite) == OpenFlags.ReadWrite;
+            if (isReadWrite && storeLocation == StoreLocation.LocalMachine)
+            {
+                // All LocalMachine stores are read-only from an Android application's perspective
+                throw new CryptographicException(
+                    SR.Cryptography_Unix_X509_MachineStoresReadOnly,
+                    new PlatformNotSupportedException(SR.Cryptography_Unix_X509_MachineStoresReadOnly));
+            }
+
+            StringComparer ordinalIgnoreCase = StringComparer.OrdinalIgnoreCase;
+            switch (storeLocation)
+            {
+                case StoreLocation.CurrentUser:
+                {
+                    // Matches Unix behaviour of getting a disallowed store that is always empty.
+                    if (ordinalIgnoreCase.Equals(X509Store.DisallowedStoreName, storeName))
+                    {
+                        return new UnsupportedDisallowedStore(openFlags);
+                    }
+
+                    if (ordinalIgnoreCase.Equals(X509Store.MyStoreName, storeName))
+                    {
+                        return AndroidKeyStore.OpenDefault(openFlags);
+                    }
+
+                    if (ordinalIgnoreCase.Equals(X509Store.RootStoreName, storeName))
+                    {
+                        // Android only allows updating the trusted store through the built-in settings application
+                        if (isReadWrite)
+                        {
+                            throw new CryptographicException(SR.Security_AccessDenied);
+                        }
+
+                        return new TrustedStore(storeLocation);
+                    }
+                    break;
+                }
+                case StoreLocation.LocalMachine:
+                {
+                    if (ordinalIgnoreCase.Equals(X509Store.RootStoreName, storeName))
+                    {
+                        return new TrustedStore(storeLocation);
+                    }
+
+                    break;
+                }
+            }
+
+            if ((openFlags & OpenFlags.OpenExistingOnly) == OpenFlags.OpenExistingOnly)
+                throw new CryptographicException(SR.Cryptography_X509_StoreNotFound);
+
+            string message = SR.Format(SR.Cryptography_X509_StoreCannotCreate, storeName, storeLocation);
+            throw new CryptographicException(message, new PlatformNotSupportedException(message));
         }
 
         private static ICertificatePal[] ReadPkcs12Collection(ReadOnlySpan<byte> rawData, SafePasswordHandle password)
index 0cf1cca..0928ced 100644 (file)
@@ -423,70 +423,27 @@ namespace Internal.Cryptography.Pal
             }
         }
 
-        internal sealed class UnsupportedDisallowedStore : IStorePal
+        internal static IStorePal OpenDisallowedStore(OpenFlags openFlags)
         {
-            private readonly bool _readOnly;
-
-            internal UnsupportedDisallowedStore(OpenFlags openFlags)
+            string storePath = GetStorePath(X509Store.DisallowedStoreName);
+            try
             {
-                // ReadOnly is 0x00, so it is implicit unless either ReadWrite or MaxAllowed
-                // was requested.
-                OpenFlags writeFlags = openFlags & (OpenFlags.ReadWrite | OpenFlags.MaxAllowed);
-
-                if (writeFlags == OpenFlags.ReadOnly)
+                if (Directory.Exists(storePath))
                 {
-                    _readOnly = true;
-                }
-
-                string storePath = GetStorePath(X509Store.DisallowedStoreName);
-
-                try
-                {
-                    if (Directory.Exists(storePath))
+                    // If it has no files, leave it alone.
+                    foreach (string filePath in Directory.EnumerateFiles(storePath))
                     {
-                        // If it has no files, leave it alone.
-                        foreach (string filePath in Directory.EnumerateFiles(storePath))
-                        {
-                            string msg = SR.Format(SR.Cryptography_Unix_X509_DisallowedStoreNotEmpty, storePath);
-                            throw new CryptographicException(msg, new PlatformNotSupportedException(msg));
-                        }
+                        string msg = SR.Format(SR.Cryptography_Unix_X509_DisallowedStoreNotEmpty, storePath);
+                        throw new CryptographicException(msg, new PlatformNotSupportedException(msg));
                     }
                 }
-                catch (IOException)
-                {
-                    // Suppress the exception, treat the store as empty.
-                }
-            }
-
-            public void Dispose()
-            {
-                // Nothing to do.
-            }
-
-            public void CloneTo(X509Certificate2Collection collection)
-            {
-                // Never show any data.
-            }
-
-            public void Add(ICertificatePal cert)
-            {
-                if (_readOnly)
-                {
-                    throw new CryptographicException(SR.Cryptography_X509_StoreReadOnly);
-                }
-
-                throw new CryptographicException(
-                    SR.Cryptography_Unix_X509_NoDisallowedStore,
-                    new PlatformNotSupportedException(SR.Cryptography_Unix_X509_NoDisallowedStore));
             }
-
-            public void Remove(ICertificatePal cert)
+            catch (IOException)
             {
-                // Remove never throws if it does no measurable work.
-                // Since CloneTo always says the store is empty, no measurable work is ever done.
+                // Suppress the exception, treat the store as empty.
             }
 
-            SafeHandle? IStorePal.SafeHandle { get; }
+            return new UnsupportedDisallowedStore(openFlags);
         }
     }
 }
index d0f583d..749beed 100644 (file)
@@ -139,7 +139,7 @@ namespace Internal.Cryptography.Pal
             {
                 if (X509Store.DisallowedStoreName.Equals(storeName, StringComparison.OrdinalIgnoreCase))
                 {
-                    return new DirectoryBasedStoreProvider.UnsupportedDisallowedStore(openFlags);
+                    return DirectoryBasedStoreProvider.OpenDisallowedStore(openFlags);
                 }
 
                 return new DirectoryBasedStoreProvider(storeName, openFlags);
diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnsupportedDisallowedStore.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnsupportedDisallowedStore.cs
new file mode 100644 (file)
index 0000000..fbdcd08
--- /dev/null
@@ -0,0 +1,61 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+
+namespace Internal.Cryptography.Pal
+{
+    internal sealed class UnsupportedDisallowedStore : IStorePal
+    {
+        private readonly bool _readOnly;
+
+        internal UnsupportedDisallowedStore(OpenFlags openFlags)
+        {
+            // ReadOnly is 0x00, so it is implicit unless either ReadWrite or MaxAllowed
+            // was requested.
+            OpenFlags writeFlags = openFlags & (OpenFlags.ReadWrite | OpenFlags.MaxAllowed);
+
+            if (writeFlags == OpenFlags.ReadOnly)
+            {
+                _readOnly = true;
+            }
+        }
+
+        public void Dispose()
+        {
+            // Nothing to do.
+        }
+
+        public void CloneTo(X509Certificate2Collection collection)
+        {
+            // Never show any data.
+        }
+
+        public void Add(ICertificatePal cert)
+        {
+            if (_readOnly)
+            {
+                throw new CryptographicException(SR.Cryptography_X509_StoreReadOnly);
+            }
+
+            throw new CryptographicException(
+                SR.Cryptography_Unix_X509_NoDisallowedStore,
+                new PlatformNotSupportedException(SR.Cryptography_Unix_X509_NoDisallowedStore));
+        }
+
+        public void Remove(ICertificatePal cert)
+        {
+            // Remove never throws if it does no measurable work.
+            // Since CloneTo always says the store is empty, no measurable work is ever done.
+        }
+
+        SafeHandle? IStorePal.SafeHandle { get; }
+    }
+}
index 6d619e4..cd76a2a 100644 (file)
   <data name="Cryptography_X509_StoreReadOnly" xml:space="preserve">
     <value>The X509 certificate store is read-only.</value>
   </data>
+  <data name="Cryptography_X509_StoreRemoveFailure" xml:space="preserve">
+    <value>The X509 certificate could not be removed from the store.</value>
+  </data>
   <data name="Cryptography_X509_StoreCannotCreate" xml:space="preserve">
     <value>The platform does not have a definition for an X509 certificate store named '{0}' with a StoreLocation of '{1}', and does not support creating it.</value>
   </data>
+  <data name="Cryptography_X509_StoreDSAPrivateKeyNotSupported" xml:space="preserve">
+    <value>Adding a DSA private key to the store is not supported on this platform.</value>
+  </data>
+  <data name="Cryptography_X509_StoreEnumerateFailure" xml:space="preserve">
+    <value>Failed to enumerate certificates from the store.</value>
+  </data>
   <data name="Cryptography_X509_NoPemCertificate" xml:space="preserve">
     <value>The certificate contents do not contain a PEM with a CERTIFICATE label, or the content is malformed.</value>
   </data>
index 20dbf62..88e77c6 100644 (file)
     <Compile Include="Internal\Cryptography\Pal.Unix\PkcsFormatReader.cs" />
     <Compile Include="Internal\Cryptography\Pal.Unix\SingleCertLoader.cs" />
     <Compile Include="Internal\Cryptography\Pal.Unix\StorePal.cs" />
+    <Compile Include="Internal\Cryptography\Pal.Unix\UnsupportedDisallowedStore.cs" />
     <Compile Include="Internal\Cryptography\Pal.Unix\X509Pal.cs" />
     <Compile Include="Internal\Cryptography\Pal.Unix\X509Persistence.cs" />
     <Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs"
     <Compile Include="Internal\Cryptography\Pal.Android\ChainPal.cs" />
     <Compile Include="Internal\Cryptography\Pal.Android\FindPal.cs" />
     <Compile Include="Internal\Cryptography\Pal.Android\StorePal.cs" />
+    <Compile Include="Internal\Cryptography\Pal.Android\StorePal.AndroidKeyStore.cs" />
     <Compile Include="Internal\Cryptography\Pal.Android\StorePal.ExportPal.cs" />
     <Compile Include="Internal\Cryptography\Pal.Android\StorePal.LoaderPal.cs" />
+    <Compile Include="Internal\Cryptography\Pal.Android\StorePal.TrustedStore.cs" />
     <Compile Include="Internal\Cryptography\Pal.Android\X509Pal.cs" />
     <Compile Include="Internal\Cryptography\Pal.Unix\CertificateData.ManagedDecode.cs" />
+    <Compile Include="Internal\Cryptography\Pal.Unix\UnsupportedDisallowedStore.cs" />
     <Compile Include="$(CommonPath)Internal\Cryptography\AsymmetricAlgorithmHelpers.Hash.cs"
              Link="Common\Internal\Cryptography\AsymmetricAlgorithmHelpers.Hash.cs" />
     <Compile Include="$(CommonPath)Interop\Android\Interop.JObjectLifetime.cs"
              Link="Common\Interop\Android\System.Security.Cryptography.Native.Android\Interop.Rsa.cs" />
     <Compile Include="$(CommonPath)Interop\Android\System.Security.Cryptography.Native.Android\Interop.X509.cs"
              Link="Common\Interop\Android\System.Security.Cryptography.Native.Android\Interop.X509.cs" />
+    <Compile Include="$(CommonPath)Interop\Android\System.Security.Cryptography.Native.Android\Interop.X509Store.cs"
+             Link="Common\Interop\Android\System.Security.Cryptography.Native.Android\Interop.X509Store.cs" />
     <Compile Include="$(CommonPath)Interop\Android\System.Security.Cryptography.Native.Android\SafeKeyHandle.cs"
              Link="Common\Interop\Android\System.Security.Cryptography.Native.Android\SafeKeyHandle.cs" />
     <Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs"
index f414305..285ce52 100644 (file)
@@ -350,10 +350,14 @@ namespace System.Security.Cryptography.X509Certificates
         public virtual byte[] GetCertHash(HashAlgorithmName hashAlgorithm)
         {
             ThrowIfInvalid();
+            return GetCertHash(hashAlgorithm, Pal!);
+        }
 
+        private static byte[] GetCertHash(HashAlgorithmName hashAlgorithm, ICertificatePalCore certPal)
+        {
             using (IncrementalHash hasher = IncrementalHash.CreateHash(hashAlgorithm))
             {
-                hasher.AppendData(Pal!.RawData);
+                hasher.AppendData(certPal.RawData);
                 return hasher.GetHashAndReset();
             }
         }
@@ -382,7 +386,12 @@ namespace System.Security.Cryptography.X509Certificates
         {
             ThrowIfInvalid();
 
-            return GetCertHash(hashAlgorithm).ToHexStringUpper();
+            return GetCertHashString(hashAlgorithm, Pal!);
+        }
+
+        internal static string GetCertHashString(HashAlgorithmName hashAlgorithm, ICertificatePalCore certPal)
+        {
+            return GetCertHash(hashAlgorithm, certPal).ToHexStringUpper();
         }
 
         // Only use for internal purposes when the returned byte[] will not be mutated
index 21c115a..e58fcfe 100644 (file)
@@ -4,7 +4,13 @@
     <DefineConstants>$(DefineConstants);HAVE_THUMBPRINT_OVERLOADS</DefineConstants>
     <DefineConstants Condition="'$(TargetsUnix)' == 'true'">$(DefineConstants);Unix</DefineConstants>
     <IncludeRemoteExecutor>true</IncludeRemoteExecutor>
-    <TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser;$(NetCoreAppCurrent)-OSX;$(NetCoreAppCurrent)-iOS;$(NetCoreAppCurrent)-tvOS</TargetFrameworks>
+    <TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Android;$(NetCoreAppCurrent)-Browser;$(NetCoreAppCurrent)-OSX;$(NetCoreAppCurrent)-iOS;$(NetCoreAppCurrent)-tvOS</TargetFrameworks>
+  </PropertyGroup>
+  <PropertyGroup>
+    <!-- Use Android native crypto when ANDROID_OPENSSL_AAR is not defined.
+         TODO: [AndroidCrypto] Remove ANDROID_OPENSSL_AAR check once Android native crypto is complete -->
+    <UseAndroidCrypto Condition="'$(TargetsAndroid)' == 'true' and '$(ANDROID_OPENSSL_AAR)' == ''">true</UseAndroidCrypto>
+    <UseAppleCrypto Condition="'$(TargetsOSX)' == 'true' or '$(TargetsiOS)' == 'true' or '$(TargetstvOS)' == 'true'">true</UseAppleCrypto>
   </PropertyGroup>
   <Import Project="$(CommonPath)System\Security\Cryptography\Asn1Reader\System.Security.Cryptography.Asn1Reader.Shared.projitems" />
   <ItemGroup>
@@ -70,7 +76,7 @@
     <Compile Include="RSAOther.cs" />
     <Compile Include="TestDataGenerator.cs" />
   </ItemGroup>
-  <ItemGroup Condition="'$(TargetsUnix)' == 'true' and '$(TargetsOSX)' != 'true' and '$(TargetsiOS)' != 'true' and '$(TargetstvOS)' != 'true'">
+  <ItemGroup Condition="'$(TargetsUnix)' == 'true' and '$(UseAndroidCrypto)' != 'true' and '$(UseAppleCrypto)' != 'true'">
     <Compile Include="X509FilesystemTests.Unix.cs" />
     <Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs"
              Link="Common\Interop\Unix\Interop.Libraries.cs" />
              Link="Common\System\IO\PersistedFiles.Names.Unix.cs" />
     <Compile Include="HostnameMatchTests.Unix.cs" />
   </ItemGroup>
-  <ItemGroup Condition=" '$(TargetsOSX)' == 'true' or '$(TargetsiOS)' == 'true' or '$(TargetstvOS)' == 'true'">
+  <ItemGroup Condition="'$(UseAndroidCrypto)' == 'true' or '$(UseAppleCrypto)' == 'true'">
+    <Compile Include="X509StoreMutableTests.cs" />
+  </ItemGroup>
+  <ItemGroup Condition="'$(UseAndroidCrypto)' == 'true'">
+    <Compile Include="X509StoreMutableTests.Android.cs" />
+  </ItemGroup>
+  <ItemGroup Condition="'$(UseAppleCrypto)' == 'true'">
     <Compile Include="X509StoreMutableTests.OSX.cs" />
     <Compile Include="$(CommonPath)Interop\OSX\Interop.CoreFoundation.cs"
              Link="Common\Interop\OSX\Interop.CoreFoundation.cs" />
diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/X509StoreMutableTests.Android.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/X509StoreMutableTests.Android.cs
new file mode 100644 (file)
index 0000000..76e6082
--- /dev/null
@@ -0,0 +1,56 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Xunit;
+
+namespace System.Security.Cryptography.X509Certificates.Tests
+{
+    public static partial class X509StoreMutableTests
+    {
+        public static bool PermissionsAllowStoreWrite { get; } = true;
+
+        [Theory]
+        [InlineData(nameof(TestData.RsaCertificate), TestData.RsaCertificate, TestData.RsaPkcs8Key)]
+        [InlineData(nameof(TestData.EcDhCertificate), TestData.EcDhCertificate, TestData.EcDhPkcs8Key)]
+        [InlineData(nameof(TestData.ECDsaCertificate), TestData.ECDsaCertificate, TestData.ECDsaPkcs8Key)]
+        public static void AddRemove_CertWithPrivateKey(string testCase, string certPem, string keyPem)
+        {
+            _ = testCase;
+            using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
+            using (var cert = X509Certificate2.CreateFromPem(certPem, keyPem))
+            {
+                store.Open(OpenFlags.ReadWrite);
+
+                // Make sure cert is not already in the store
+                store.Remove(cert);
+                Assert.False(IsCertInStore(cert, store), "Certificate should not be found on pre-condition");
+
+                // Add
+                store.Add(cert);
+                Assert.True(IsCertInStore(cert, store), "Certificate should be found after add");
+                Assert.True(StoreHasPrivateKey(store, cert), "Certificate in store should have a private key");
+
+                // Remove
+                store.Remove(cert);
+                Assert.False(IsCertInStore(cert, store), "Certificate should not be found after remove");
+            }
+        }
+
+        [Fact]
+        public static void Add_CertWithPrivateKey_NotSupportedAlgorithm()
+        {
+            using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
+            using (var cert = X509Certificate2.CreateFromPem(TestData.DsaCertificate, TestData.DsaPkcs8Key))
+            {
+                store.Open(OpenFlags.ReadWrite);
+
+                // Make sure cert is not already in the store
+                store.Remove(cert);
+                Assert.False(IsCertInStore(cert, store), "Certificate should not be found on pre-condition");
+
+                // Add - throws PNSE
+                Assert.Throws<PlatformNotSupportedException>(() => store.Add(cert));
+            }
+        }
+    }
+}
index 422c71a..7678da5 100644 (file)
@@ -5,9 +5,8 @@ using Xunit;
 
 namespace System.Security.Cryptography.X509Certificates.Tests
 {
-    [OuterLoop("Modifies system state")]
     [PlatformSpecific(TestPlatforms.OSX)]
-    public static class X509StoreMutableTests_OSX
+    public static partial class X509StoreMutableTests
     {
         public static bool PermissionsAllowStoreWrite { get; } = TestPermissions();
 
@@ -117,133 +116,6 @@ namespace System.Security.Cryptography.X509Certificates.Tests
         }
 
         [ConditionalFact(nameof(PermissionsAllowStoreWrite))]
-        public static void AddToStoreTwice()
-        {
-            using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
-            using (var cert = new X509Certificate2(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.Exportable))
-            using (var certOnly = new X509Certificate2(cert.RawData))
-            {
-                store.Open(OpenFlags.ReadWrite);
-
-                // Defensive removal.
-                store.Remove(certOnly);
-                Assert.False(IsCertInStore(cert, store), "PtxData certificate was found on pre-condition");
-
-                store.Add(cert);
-                Assert.True(IsCertInStore(certOnly, store), "PtxData certificate was found after add");
-
-                // No exception for duplicate item.
-                store.Add(cert);
-
-                // Cleanup
-                store.Remove(certOnly);
-            }
-        }
-
-        [ConditionalFact(nameof(PermissionsAllowStoreWrite))]
-        public static void AddPrivateAfterPublic()
-        {
-            using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
-            using (var cert = new X509Certificate2(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.Exportable))
-            using (var certOnly = new X509Certificate2(cert.RawData))
-            {
-                store.Open(OpenFlags.ReadWrite);
-
-                // Defensive removal.
-                store.Remove(certOnly);
-                Assert.False(IsCertInStore(cert, store), "PtxData certificate was found on pre-condition");
-
-                store.Add(certOnly);
-                Assert.True(IsCertInStore(certOnly, store), "PtxData certificate was found after add");
-                Assert.False(StoreHasPrivateKey(store, certOnly), "Store has a private key for PfxData after public-only add");
-
-                // Add the private key
-                store.Add(cert);
-                Assert.True(StoreHasPrivateKey(store, certOnly), "Store has a private key for PfxData after PFX add");
-
-                // Cleanup
-                store.Remove(certOnly);
-            }
-        }
-
-        [ConditionalFact(nameof(PermissionsAllowStoreWrite))]
-        public static void AddPublicAfterPrivate()
-        {
-            using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
-            using (var cert = new X509Certificate2(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.Exportable))
-            using (var certOnly = new X509Certificate2(cert.RawData))
-            {
-                store.Open(OpenFlags.ReadWrite);
-
-                // Defensive removal.
-                store.Remove(certOnly);
-                Assert.False(IsCertInStore(cert, store), "PtxData certificate was found on pre-condition");
-
-                // Add the private key
-                store.Add(cert);
-                Assert.True(IsCertInStore(certOnly, store), "PtxData certificate was found after add");
-                Assert.True(StoreHasPrivateKey(store, certOnly), "Store has a private key for PfxData after PFX add");
-
-                // Add the public key with no private key
-                store.Add(certOnly);
-                Assert.True(StoreHasPrivateKey(store, certOnly), "Store has a private key for PfxData after public-only add");
-
-                // Cleanup
-                store.Remove(certOnly);
-            }
-        }
-
-        [ConditionalFact(nameof(PermissionsAllowStoreWrite))]
-        public static void VerifyRemove()
-        {
-            using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
-            using (var cert = new X509Certificate2(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.Exportable))
-            {
-                store.Open(OpenFlags.ReadWrite);
-
-                // Defensive removal.  Sort of circular, but it's the best we can do.
-                store.Remove(cert);
-                Assert.False(IsCertInStore(cert, store), "PtxData certificate was found on pre-condition");
-
-                store.Add(cert);
-                Assert.True(IsCertInStore(cert, store), "PtxData certificate was found after add");
-
-                store.Remove(cert);
-                Assert.False(IsCertInStore(cert, store), "PtxData certificate was found after remove");
-            }
-        }
-
-        [ConditionalFact(nameof(PermissionsAllowStoreWrite))]
-        public static void RemovePublicDeletesPrivateKey()
-        {
-            using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
-            using (var cert = new X509Certificate2(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.Exportable))
-            using (var certOnly = new X509Certificate2(cert.RawData))
-            {
-                store.Open(OpenFlags.ReadWrite);
-
-                // Defensive removal.
-                store.Remove(cert);
-                Assert.False(IsCertInStore(cert, store), "PtxData certificate was found on pre-condition");
-
-                // Add the private key
-                store.Add(cert);
-                Assert.True(IsCertInStore(cert, store), "PtxData certificate was found after add");
-
-                store.Remove(certOnly);
-                Assert.False(IsCertInStore(cert, store), "PtxData certificate was found after remove");
-
-                // Add back the public key only
-                store.Add(certOnly);
-                Assert.True(IsCertInStore(cert, store), "PtxData certificate was found after public-only add");
-                Assert.False(StoreHasPrivateKey(store, cert), "Store has a private key for cert after public-only add");
-
-                // Cleanup
-                store.Remove(certOnly);
-            }
-        }
-
-        [ConditionalFact(nameof(PermissionsAllowStoreWrite))]
         public static void CustomStore_ReadWrite()
         {
             using (var store = new X509Store("CustomKeyChain_CoreFX", StoreLocation.CurrentUser))
@@ -320,47 +192,6 @@ namespace System.Security.Cryptography.X509Certificates.Tests
                 Assert.ThrowsAny<CryptographicException>(() => store.Open(OpenFlags.ReadWrite));
         }
 
-        private static bool StoreHasPrivateKey(X509Store store, X509Certificate2 forCert)
-        {
-            using (ImportedCollection coll = new ImportedCollection(store.Certificates))
-            {
-                foreach (X509Certificate2 storeCert in coll.Collection)
-                {
-                    if (forCert.Equals(storeCert))
-                    {
-                        return storeCert.HasPrivateKey;
-                    }
-                }
-            }
-
-            Assert.True(false, $"Certificate ({forCert.Subject}) exists in the store");
-            return false;
-        }
-
-        private static bool IsCertInStore(X509Certificate2 cert, X509Store store)
-        {
-            using (ImportedCollection coll = new ImportedCollection(store.Certificates))
-            {
-                foreach (X509Certificate2 storeCert in coll.Collection)
-                {
-                    if (cert.Equals(storeCert))
-                    {
-                        return true;
-                    }
-                }
-            }
-
-            return false;
-        }
-
-        private static int GetStoreCertificateCount(X509Store store)
-        {
-            using (var coll = new ImportedCollection(store.Certificates))
-            {
-                return coll.Collection.Count;
-            }
-        }
-
         private class TemporaryX509Store : IDisposable
         {
             private X509Store _store;
diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/X509StoreMutableTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/X509StoreMutableTests.cs
new file mode 100644 (file)
index 0000000..e49b1c8
--- /dev/null
@@ -0,0 +1,183 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Xunit;
+
+namespace System.Security.Cryptography.X509Certificates.Tests
+{
+    [OuterLoop("Modifies system state")]
+    public static partial class X509StoreMutableTests
+    {
+        [ConditionalFact(nameof(PermissionsAllowStoreWrite))]
+        public static void AddToStoreTwice()
+        {
+            using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
+            using (var cert = new X509Certificate2(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.Exportable))
+            using (var certOnly = new X509Certificate2(cert.RawData))
+            {
+                store.Open(OpenFlags.ReadWrite);
+
+                // Defensive removal.
+                store.Remove(certOnly);
+                Assert.False(IsCertInStore(cert, store), "PfxData certificate was found on pre-condition");
+
+                store.Add(cert);
+                Assert.True(IsCertInStore(certOnly, store), "PfxData certificate was found after add");
+
+                // No exception for duplicate item.
+                store.Add(cert);
+
+                // Cleanup
+                store.Remove(certOnly);
+            }
+        }
+
+        [ConditionalFact(nameof(PermissionsAllowStoreWrite))]
+        public static void AddPrivateAfterPublic()
+        {
+            using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
+            using (var cert = new X509Certificate2(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.Exportable))
+            using (var certOnly = new X509Certificate2(cert.RawData))
+            {
+                store.Open(OpenFlags.ReadWrite);
+
+                // Defensive removal.
+                store.Remove(certOnly);
+                Assert.False(IsCertInStore(cert, store), "PfxData certificate was found on pre-condition");
+
+                store.Add(certOnly);
+                Assert.True(IsCertInStore(certOnly, store), "PfxData certificate was found after add");
+                Assert.False(StoreHasPrivateKey(store, certOnly), "Store has a private key for PfxData after public-only add");
+
+                // Add the private key
+                store.Add(cert);
+                Assert.True(StoreHasPrivateKey(store, certOnly), "Store has a private key for PfxData after PFX add");
+
+                // Cleanup
+                store.Remove(certOnly);
+            }
+        }
+
+        [ConditionalFact(nameof(PermissionsAllowStoreWrite))]
+        public static void AddPublicAfterPrivate()
+        {
+            using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
+            using (var cert = new X509Certificate2(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.Exportable))
+            using (var certOnly = new X509Certificate2(cert.RawData))
+            {
+                store.Open(OpenFlags.ReadWrite);
+
+                // Defensive removal.
+                store.Remove(certOnly);
+                Assert.False(IsCertInStore(cert, store), "PfxData certificate was found on pre-condition");
+
+                // Add the private key
+                store.Add(cert);
+                Assert.True(IsCertInStore(certOnly, store), "PfxData certificate was found after add");
+                Assert.True(StoreHasPrivateKey(store, certOnly), "Store has a private key for PfxData after PFX add");
+
+                // Add the public key with no private key
+                store.Add(certOnly);
+                Assert.True(StoreHasPrivateKey(store, certOnly), "Store has a private key for PfxData after public-only add");
+
+                // Cleanup
+                store.Remove(certOnly);
+            }
+        }
+
+        [ConditionalTheory(nameof(PermissionsAllowStoreWrite))]
+        [InlineData(true)]
+        [InlineData(false)]
+        public static void VerifyRemove(bool withPrivateKey)
+        {
+            using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
+            using (var certWithPrivateKey = new X509Certificate2(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.Exportable))
+            using (var certOnly = new X509Certificate2(certWithPrivateKey.RawData))
+            {
+                X509Certificate2 cert = withPrivateKey ? certWithPrivateKey : certOnly;
+                store.Open(OpenFlags.ReadWrite);
+
+                // Defensive removal.  Sort of circular, but it's the best we can do.
+                store.Remove(cert);
+                Assert.False(IsCertInStore(cert, store), "PfxData certificate was found on pre-condition");
+
+                store.Add(cert);
+                Assert.True(IsCertInStore(cert, store), "PfxData certificate was found after add");
+
+                store.Remove(cert);
+                Assert.False(IsCertInStore(cert, store), "PfxData certificate was found after remove");
+            }
+        }
+
+        [ConditionalFact(nameof(PermissionsAllowStoreWrite))]
+        public static void RemovePublicDeletesPrivateKey()
+        {
+            using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
+            using (var cert = new X509Certificate2(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.Exportable))
+            using (var certOnly = new X509Certificate2(cert.RawData))
+            {
+                store.Open(OpenFlags.ReadWrite);
+
+                // Defensive removal.
+                store.Remove(cert);
+                Assert.False(IsCertInStore(cert, store), "PfxData certificate was found on pre-condition");
+
+                // Add the private key
+                store.Add(cert);
+                Assert.True(IsCertInStore(cert, store), "PfxData certificate was found after add");
+
+                store.Remove(certOnly);
+                Assert.False(IsCertInStore(cert, store), "PfxData certificate was found after remove");
+
+                // Add back the public key only
+                store.Add(certOnly);
+                Assert.True(IsCertInStore(cert, store), "PfxData certificate was found after public-only add");
+                Assert.False(StoreHasPrivateKey(store, cert), "Store has a private key for cert after public-only add");
+
+                // Cleanup
+                store.Remove(certOnly);
+            }
+        }
+
+        private static bool StoreHasPrivateKey(X509Store store, X509Certificate2 forCert)
+        {
+            using (ImportedCollection coll = new ImportedCollection(store.Certificates))
+            {
+                foreach (X509Certificate2 storeCert in coll.Collection)
+                {
+                    if (forCert.Equals(storeCert))
+                    {
+                        return storeCert.HasPrivateKey;
+                    }
+                }
+            }
+
+            Assert.True(false, $"Certificate ({forCert.Subject}) exists in the store");
+            return false;
+        }
+
+        private static bool IsCertInStore(X509Certificate2 cert, X509Store store)
+        {
+            using (ImportedCollection coll = new ImportedCollection(store.Certificates))
+            {
+                foreach (X509Certificate2 storeCert in coll.Collection)
+                {
+                    if (cert.Equals(storeCert))
+                    {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
+
+        private static int GetStoreCertificateCount(X509Store store)
+        {
+            using (var coll = new ImportedCollection(store.Certificates))
+            {
+                return coll.Collection.Count;
+            }
+        }
+    }
+}
index 71b13d8..ea0906d 100644 (file)
@@ -9,6 +9,7 @@ using System.Diagnostics;
 using System.IO;
 using System.Runtime.InteropServices;
 using Microsoft.DotNet.RemoteExecutor;
+using Test.Cryptography;
 using Xunit;
 
 namespace System.Security.Cryptography.X509Certificates.Tests
@@ -94,9 +95,9 @@ namespace System.Security.Cryptography.X509Certificates.Tests
             }
         }
 
-        [PlatformSpecific(TestPlatforms.AnyUnix & ~TestPlatforms.OSX)] // API not supported via OpenSSL
+        [PlatformSpecific(PlatformSupport.OpenSSL)] // API not supported via OpenSSL
         [Fact]
-        public static void Constructor_StoreHandle_Unix()
+        public static void Constructor_StoreHandle_OpenSSL()
         {
             using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
             {