DSA,
EC,
RSA,
+ UnknownAlgorithm = -1,
}
[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509PublicKey")]
--- /dev/null
+// 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()
+ {
+ }
+ }
+}
pal_ssl.c
pal_sslstream.c
pal_x509.c
+ pal_x509store.c
)
add_library(System.Security.Cryptography.Native.Android
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);
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
} 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.
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;
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;
// java/security/cert/X509Certificate
jclass g_X509CertClass;
+jmethodID g_X509CertEquals;
jmethodID g_X509CertGetEncoded;
jmethodID g_X509CertGetPublicKey;
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;
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;");
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");
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;");
// 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;
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;
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;
return FAIL;
}
- RSA* rsa = AndroidCryptoNative_NewRsaFromPublicKey(env, publicKey);
+ RSA* rsa = AndroidCryptoNative_NewRsaFromKeys(env, publicKey, NULL /*privateKey*/);
(*env)->DeleteLocalRef(env, publicKey);
return 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);
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);
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;
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
--- /dev/null
+// 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;
+}
--- /dev/null
+// 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);
}
}
- 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;
--- /dev/null
+// 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();
+ }
+ }
+ }
+}
--- /dev/null
+// 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();
+ }
+ }
+ }
+}
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)
}
}
- 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);
}
}
}
{
if (X509Store.DisallowedStoreName.Equals(storeName, StringComparison.OrdinalIgnoreCase))
{
- return new DirectoryBasedStoreProvider.UnsupportedDisallowedStore(openFlags);
+ return DirectoryBasedStoreProvider.OpenDisallowedStore(openFlags);
}
return new DirectoryBasedStoreProvider(storeName, openFlags);
--- /dev/null
+// 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; }
+ }
+}
<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>
<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"
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();
}
}
{
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
<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>
<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" />
--- /dev/null
+// 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));
+ }
+ }
+ }
+}
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();
}
[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))
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;
--- /dev/null
+// 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;
+ }
+ }
+ }
+}
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.DotNet.RemoteExecutor;
+using Test.Cryptography;
using Xunit;
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))
{