Unable to access certificate deployed by SOTI MobiControl in Android app
I'm trying to access a certificate that was deployed through SOTI MobiControl in my Xamarin.Android/.NET MAUI Android app. The certificate is visible in the SOTI MobiControl console, but I can't access it from my app code.
What I've tried:
- Using the Android KeyStore API to search through different keystore types:
public class MdmCertificateKeyRetriever
{
private readonly Context _context;
private readonly string _certificatePassword;
public MdmCertificateKeyRetriever(Context context, string certificatePassword)
{
_context = context;
_certificatePassword = certificatePassword;
}
public bool IsCertificateInstalled(string certificateAlias)
{
// Prioritize the keystores where the certificate is most likely to be found
string[] keystoreTypes = new[]
{
"AndroidKeyStore",
"PKCS12",
KeyStore.DefaultType,
"AndroidCAStore",
};
foreach (var keystoreType in keystoreTypes.Where(k => !string.IsNullOrEmpty(k)))
{
try
{
// Try to access the keystore with the provided password
var keyStore = KeyStore.GetInstance(keystoreType);
// Try loading with password first, then fall back to null password
bool keystoreLoaded = false;
// Only try with password for keystore types that support it (not AndroidKeyStore)
if (keystoreType != "AndroidKeyStore")
{
try
{
keyStore.Load(null, _certificatePassword.ToCharArray());
keystoreLoaded = true;
MainThread.BeginInvokeOnMainThread(async () =>
{
await Application.Current.MainPage.DisplayAlert(
"Keystore Loaded",
$"Successfully loaded keystore: {keystoreType} with password",
"OK");
});
}
catch (Exception ex)
{
Log.Warning($"Couldn't load keystore with password: {ex.Message}");
}
}
// If not loaded with password, try with null password
if (!keystoreLoaded)
{
try
{
keyStore.Load(null, null);
keystoreLoaded = true;
MainThread.BeginInvokeOnMainThread(async () =>
{
await Application.Current.MainPage.DisplayAlert(
"Keystore Loaded",
$"Successfully loaded keystore: {keystoreType} with null password",
"OK");
});
}
catch (Exception ex)
{
Log.Warning($"Couldn't load keystore with null password: {ex.Message}");
continue; // Skip to next keystore type
}
}
// Check if the certificate exists by alias
if (keyStore.ContainsAlias(certificateAlias))
{
// Get certificate details and display them
string details = GetCertificateDetails(keyStore, certificateAlias);
MainThread.BeginInvokeOnMainThread(async () =>
{
await Application.Current.MainPage.DisplayAlert(
"Certificate Found",
details,
"OK");
});
return true;
}
else
{
// List all available aliases for debugging
var aliases = new System.Collections.Generic.List<string>();
var aliasEnum = keyStore.Aliases();
while (aliasEnum != null && aliasEnum.HasMoreElements)
{
string alias = aliasEnum.NextElement()?.ToString() ?? string.Empty;
if (!string.IsNullOrEmpty(alias))
{
aliases.Add(alias);
}
}
if (aliases.Count > 0)
{
MainThread.BeginInvokeOnMainThread(async () =>
{
await Application.Current.MainPage.DisplayAlert(
$"Aliases in {keystoreType}",
string.Join("\n", aliases),
"OK");
});
}
}
}
catch (Exception ex)
{
Log.Error($"Error checking keystore {keystoreType}: {ex.Message}");
}
}
// Certificate not found in any keystore
MainThread.BeginInvokeOnMainThread(async () =>
{
await Application.Current.MainPage.DisplayAlert(
"Certificate Not Found",
$"Certificate with alias '{certificateAlias}' not found in any keystore.",
"OK");
});
return false;
}
private string GetCertificateDetails(KeyStore keyStore, string alias)
{
var details = new System.Text.StringBuilder();
details.AppendLine($"Certificate Alias: {alias}");
details.AppendLine($"Keystore Type: {keyStore.Type}");
try
{
// Try to get certificate information
if (keyStore.IsCertificateEntry(alias))
{
var cert = keyStore.GetCertificate(alias);
if (cert != null)
{
details.AppendLine($"Certificate Type: {cert.Type}");
//details.AppendLine($"Issuer: {cert.IssuerDN?.Name ?? "unknown"}");
//details.AppendLine($"Subject: {cert.SubjectDN?.Name ?? "unknown"}");
}
}
// Try to access private key
if (keyStore.IsKeyEntry(alias))
{
details.AppendLine("Contains private key: Yes");
try
{
// Try to get the private key using the password first
IKey key = null;
bool keyRetrieved = false;
if (keyStore.Type != "AndroidKeyStore")
{
try
{
key = keyStore.GetKey(alias, _certificatePassword.ToCharArray());
keyRetrieved = true;
}
catch (Exception ex)
{
details.AppendLine($"Could not access private key with password: {ex.Message}");
}
}
// If not retrieved with password or it's AndroidKeyStore, try with null
if (!keyRetrieved)
{
try
{
key = keyStore.GetKey(alias, null);
keyRetrieved = true;
}
catch (Exception ex)
{
details.AppendLine($"Could not access private key with null password: {ex.Message}");
}
}
if (key is IPrivateKey privateKey)
{
details.AppendLine($"Key Algorithm: {privateKey.Algorithm}");
details.AppendLine($"Key Format: {privateKey.Format}");
// Get the private key in PEM format
string pemKey = ConvertPrivateKeyToPem(privateKey);
if (!string.IsNullOrEmpty(pemKey))
{
details.AppendLine("\nPrivate Key (PEM format):");
details.AppendLine(pemKey);
}
}
}
catch (Exception ex)
{
details.AppendLine($"Could not access private key: {ex.Message}");
}
}
else
{
details.AppendLine("Contains private key: No");
}
}
catch (Exception ex)
{
details.AppendLine($"Error getting certificate details: {ex.Message}");
}
return details.ToString();
}
public string? GetPrivateKey(string alias)
{
// Prioritize the keystores where the certificate is most likely to be found
string[] keystoreTypes = new[]
{
"AndroidKeyStore",
"PKCS12",
KeyStore.DefaultType,
"AndroidCAStore",
};
foreach (var keystoreType in keystoreTypes.Where(k => !string.IsNullOrEmpty(k)))
{
try
{
var keyStore = KeyStore.GetInstance(keystoreType);
bool keystoreLoaded = false;
// Try with password first (except for AndroidKeyStore)
if (keystoreType != "AndroidKeyStore")
{
try
{
keyStore.Load(null, _certificatePassword.ToCharArray());
keystoreLoaded = true;
MainThread.BeginInvokeOnMainThread(async () =>
{
await Application.Current.MainPage.DisplayAlert(
"Keystore Loaded for Key Retrieval",
$"Successfully loaded {keystoreType} with password",
"OK");
});
}
catch (Exception ex)
{
Log.Warning($"Could not load keystore {keystoreType} with password: {ex.Message}");
}
}
// If not loaded with password, try with null
if (!keystoreLoaded)
{
try
{
keyStore.Load(null, null);
keystoreLoaded = true;
MainThread.BeginInvokeOnMainThread(async () =>
{
await Application.Current.MainPage.DisplayAlert(
"Keystore Loaded for Key Retrieval",
$"Successfully loaded {keystoreType} with null password",
"OK");
});
}
catch (Exception ex)
{
Log.Warning($"Could not load keystore {keystoreType} with null password: {ex.Message}");
continue; // Skip to next keystore
}
}
if (keyStore.IsKeyEntry(alias))
{
// Try to get the key with password first, then null password
IKey key = null;
bool keyRetrieved = false;
if (keystoreType != "AndroidKeyStore")
{
try
{
key = keyStore.GetKey(alias, _certificatePassword.ToCharArray());
keyRetrieved = true;
MainThread.BeginInvokeOnMainThread(async () =>
{
await Application.Current.MainPage.DisplayAlert(
"Key Retrieved",
$"Retrieved key for {alias} using password",
"OK");
});
}
catch (Exception ex)
{
Log.Warning($"Could not get key with password: {ex.Message}");
}
}
if (!keyRetrieved)
{
try
{
key = keyStore.GetKey(alias, null);
keyRetrieved = true;
MainThread.BeginInvokeOnMainThread(async () =>
{
await Application.Current.MainPage.DisplayAlert(
"Key Retrieved",
$"Retrieved key for {alias} using null password",
"OK");
});
}
catch (Exception ex)
{
Log.Warning($"Could not get key with null password: {ex.Message}");
}
}
if (key is IPrivateKey privateKey)
{
string pemKey = ConvertPrivateKeyToPem(privateKey);
MainThread.BeginInvokeOnMainThread(async () =>
{
await Application.Current.MainPage.DisplayAlert(
"Private Key Retrieved",
$"Successfully retrieved private key for {alias} from {keystoreType}",
"OK");
});
return pemKey;
}
}
}
catch (Exception ex)
{
Log.Error($"Error accessing keystore {keystoreType}: {ex.Message}");
}
}
MainThread.BeginInvokeOnMainThread(async () =>
{
await Application.Current.MainPage.DisplayAlert(
"Private Key Not Found",
$"Could not retrieve private key for alias {alias}",
"OK");
});
return null;
}
private string ConvertPrivateKeyToPem(IPrivateKey privateKey)
{
try
{
// Get the encoded private key bytes
byte[] privateKeyBytes = privateKey.GetEncoded() ?? Array.Empty<byte>();
if (privateKeyBytes.Length > 0)
{
// Convert to Base64
string base64Key = Convert.ToBase64String(privateKeyBytes, Base64FormattingOptions.InsertLineBreaks);
// Determine key type
string keyType = DetermineKeyType(privateKey);
// Format as PEM with appropriate header
var pem = $"-----BEGIN {keyType} PRIVATE KEY-----\n" +
base64Key +
$"\n-----END {keyType} PRIVATE KEY-----";
return pem;
}
}
catch (Exception ex)
{
Log.Error($"Error converting private key to PEM: {ex.Message}");
}
return string.Empty;
}
private string DetermineKeyType(IPrivateKey privateKey)
{
// Check the algorithm of the private key
string algorithm = privateKey.Algorithm ?? string.Empty;
// Map to standard PEM key types
return algorithm switch
{
"RSA" => "RSA",
"EC" => "EC",
"DSA" => "DSA",
_ => "PRIVATE"
};
}
}
Listing all available aliases in each keystore, but I don't see my certificate.
Trying both with password and without password when loading the keystone.
Checking if my app is running in the work profile vs personal profile.
Environment:
Android device with SOTI MobiControl Certificate was deployed through SOTI with the alias "Test" Certificate has a password App is deployed in release mode directly from Visual Studio (also from SOTI)
Questions:
- How can I access a certificate deployed by SOTI MobiControl in my Android app?
- Is there a special requirement or permission needed to access MDM-deployed certificates?
- Do I need to deploy my app through SOTI rather than directly from Visual Studio? (but, I already tested it)
- Should I be using a different API or approach altogether for MDM-deployed certificates?
Any help would be greatly appreciated as I've been stuck on this for a while!