I have an android Device with an X.509 client certificate installed.
When I use a browser to access our server, the Use certificate popup shows and the certifcate is used for authentication to access the site.
So I know the certificate is installed and works.
A piece of code that works in XAMARIN forms UWP does not work under android.
I have selected USE_CREDENTIALS in the Android Manifest.
Is there another permission required ?
Is there something else to do to access the User certificates on android ?
private X509Certificate2 GetClientCertificate()
{
using (X509Store userCaStore = new X509Store(StoreLocation.CurrentUser))
{
try
{
userCaStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certificatesInStore = userCaStore.Certificates;
// All personal certificates are available on UWP.
// The collection is empty on Android although a cert is installed and works in other apps.
foreach (var cert in certificatesInStore)
{
if (cert.FriendlyName == "MY_CERTNAME")
{
return cert;
}
}
return null;
}
catch
{
throw;
}
finally
{
userCaStore.Close();
}
}
}
Related
My application uses an RSA public key to encrypt the data before passed to the server. Everything works fine if I embed the public key to my solution and just use it.
However my client wants to change this public key sometimes, so I'm unable to add the PublicKey.key file to the solution as embedded resource.
My first tought was that I should install this public key to the Keystore like I install an X509Cetificate under the settings -> security -> Install user certificate method on the phone.
This method fails, because my file only contains the public key, which is not a certificate.
How can I store this public key in the Keystore? My goal is to install it, after that in would like to read it with my Xamarin Forms app in order to encrypt the data before I post to the server.
So my goal is not the set this public key to the keystore with code, it has to be there.
You could try to save public key as string into Secure Storage.
To save a value for a given key in secure storage:
try
{
await SecureStorage.SetAsync("oauth_token", "secret-oauth-token-value");
}
catch (Exception ex)
{
// Possible that device doesn't support secure storage on device.
}
To retrieve a value from secure storage:
try
{
var oauthToken = await SecureStorage.GetAsync("oauth_token");
}
catch (Exception ex)
{
// Possible that device doesn't support secure storage on device.
}
For more details, refer to Xamarin.Essentials: Secure Storage:
https://learn.microsoft.com/en-us/xamarin/essentials/secure-storage?context=xamarin%2Fandroid&tabs=ios
I want to get fingerprint byte data using android 7 api . For that I am following this tutorial : Fingerprint API Tutorial
In this tutorial I have goth directions to authenticate the user using fingerprint using fingerprint api .
public void startListening(FingerprintManager.CryptoObject cryptoObject) {
if (!isFingerprintAuthAvailable()) {
return;
}
mCancellationSignal = new CancellationSignal();
mSelfCancelled = false;
mFingerprintManager
.authenticate(cryptoObject, mCancellationSignal, 0 /* flags */, this, null);
mIcon.setImageResource(R.drawable.ic_fp_40px);
}
public void stopListening() {
if (mCancellationSignal != null) {
mSelfCancelled = true;
mCancellationSignal.cancel();
mCancellationSignal = null;
}
}
But I do not need to authenticate the fingerprint data of user using android api . I only need fingerprint byte data captured from android api . How can I get that ?
Current (and likely future) Android API's wll only allow authenticatation of fingerprints and no access to the raw fingerprint data itself. This is because of the way Android deals with fingerprint data; it is analysed exclusively in the Trusted Execution Environment (TEE). The TEE holds the only encryption key to the fingerprint data and it never leaves the TEE, so even if you can get your hands on the fingerprint data it will be encrypted.
It is also important to note that the TEE is hardware backed, either using dedicated hardware or via virtualisation on the main CPU, so even rooting your phone will not allow you to get to the data.
You can read more about the TEE here
If you really need access to raw fingerprint data, I suggest using an external scanner with an accompanying SDK such as this one. I have used this for projects before and the SDK is very good and well documented.
I have this scenario where my App needs to make requests towards a secure server (NON http(s), actually it is about SIP protocol but the question should apply to any non http(s) protocol), and I need be able to tell if the server is considered trusted, based on the System Default Trusted certificates installed in my Android device's keystore.
The problem is that after checking all the APIs Android provides for certificates (like KeyStore, KeyChain, etc) I haven't been able to find a solution.
Seems that each app, even though it can gain access to the System Default keystore of the device, it can only access it's own resources, not global, even when we are talking about TrustedCertificateEntry-type entries.
Is there anything I'm missing here?
Seems like a pretty valid use case for non-https authentication
Best regards,
Antonis
Finally, managed to find a way to do this, so let me share in case this can be useful to others. Turns out Android gives access to system wide trusted certificates. The detail here (and the reason it didn't work for me previously) was the keystore 'type' identifier that I used:
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
Which I believe was trying to find actual keys, which off course shouldn't be shared. So after some digging I found that there's a separate type, AndroidCAStore, which did the trick for me. So here's a working code excerpt, that just prints out certificates:
try {
KeyStore ks = KeyStore.getInstance("AndroidCAStore");
ks.load(null);
try {
Enumeration<String> aliases = ks.aliases();
while (aliases.hasMoreElements()) {
Certificate cert = ks.getCertificate(aliases.nextElement());
Log.e(TAG, "Certificate: " + cert.toString());
}
}
catch(Exception e) {
e.printStackTrace();
}
}
catch (IOException|NoSuchAlgorithmException|KeyStoreException|CertificateException e) {
e.printStackTrace();
}
disclaimer it is self Q&A question
Several times I faced with situation when our customers bought a certificate considered as "trusted" on iOS, but unfortunately on Android it didn't work.
What is the cause and how to solve it?
The short answer is buy common trusted certificate.
The reasons is trust store on Android devices contains different set of trusted certificates -- IOS and Android trusted certificates are different.
It can be described on sets:
A -- android trusted certificates
I -- iOS trusted certificates
AI -- intersection of trusted certificates
Therefore, we need an intersection of those two platforms.
However, this issue becomes more complicated because set of trusted certificates varys by OS version for both Android and iOS. It means we will need to look through all supported platforms and all their supported versions to find common set of supported certificates.
For iOS the list of trusted certificates are available on their official site list of supported iOS certificates
For Android I don't find the same list but it is possible to get it from runtime by code below.
public List<CertificateDto> getCertificates() {
List<CertificateDto> result = new ArrayList<>();
try {
String algorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(algorithm);
trustManagerFactory.init((KeyStore) null);
X509TrustManager xtm = (X509TrustManager) trustManagerFactory.getTrustManagers()[0];
for (X509Certificate cert : xtm.getAcceptedIssuers()) {
PublicKey publicKey = cert.getPublicKey();
int leyLength = 0;
if (publicKey instanceof RSAPublicKey) {
leyLength = ((RSAPublicKey) publicKey).getModulus().bitLength();
} else if (publicKey instanceof DSAPublicKey) {
leyLength = ((DSAPublicKey) publicKey).getY().bitLength();
}
CertNameToValidNameConverter validNameConverter = new CertNameToValidNameConverter();
CertificateDto certificate = new CertificateDto();
certificate.setSubjectName(validNameConverter.convert(cert.getSubjectDN().getName()));
certificate.setIssuerName(validNameConverter.convert(cert.getIssuerDN().getName()));
certificate.setKeyLength(leyLength);
certificate.setTypeAlg(cert.getSigAlgName());
certificate.setExpirationDate(cert.getNotAfter().getTime());
result.add(certificate);
}
} catch (NoSuchAlgorithmException e) {
Log.e(App.TAG, "Failed obtain list of certificates", e);
} catch (KeyStoreException e) {
Log.e(App.TAG, "Failed obtain list of certificates", e);
}
return result;
}
When you get the list of certificates from your supported Android OS, you will need to write script to look through all certificates and compare it with another list from different supported Android version and after that with iOS versions as well. You will need compare certificate name, issuer name, algorithm type, sign algorithm and length of the key. Expiration date is also used for validation but you can omit it.
I implemented this script by parsing certificates from excel for IOS 8,9 and Android 6.0, 4.4 and 4.1.
The final list of certificates you can find below. From ~220 IOS certificates and ~150 certificates you can use only ~65 certificates for both platform
list of supported certificates for both Android and iOS (google drive link)
I am building an Android application that communicates with an online webservice. I plan on releasing the application's source code on GitHub. For my production version, which will utilize my personal webservice I want to allow only my digitally signed apk to connect.
Is is possible to request the APK's keystore and confirm the username/password from that keystore?
If this is not possible how else can I produce this functionality?
Edit:
I have read into the class Certificate It looks like I might be able to user public/private keys to confirm an identity. But I am still unsure of an implementation
I use this --
static public String getPackageFingerPrint( Context ctx ) {
PackageManager pm = ctx.getPackageManager();
String packageName = ctx.getPackageName();
int flags = PackageManager.GET_SIGNATURES;
PackageInfo packageInfo = null;
try {
packageInfo = pm.getPackageInfo(packageName, flags);
} catch (NameNotFoundException e) {
return "";
}
Signature[] signatures = packageInfo.signatures;
byte[] cert = signatures[0].toByteArray();
InputStream input = new ByteArrayInputStream(cert);
CertificateFactory cf = null;
try {
cf = CertificateFactory.getInstance("X509");
} catch (CertificateException e) {
return "";
}
X509Certificate c = null;
try {
c = (X509Certificate) cf.generateCertificate(input);
} catch (CertificateException e) {
return "";
}
try {
MessageDigest md = MessageDigest.getInstance("SHA1");
byte[] publicKey = md.digest(c.getPublicKey().getEncoded());
StringBuffer hexString = new StringBuffer();
for (int i=0;i<publicKey.length;i++) {
String appendString = Integer.toHexString(0xFF & publicKey[i]);
if(appendString.length()==1)hexString.append("0");
hexString.append(appendString);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e1) {
return "";
}
}
The problem I see with your approach is that anyone could determine the package fingerprint or your package and send it to your web-service. A better possibility would be to use a challenge-response mechanism: Your web-service sends you a unique session-token, which your app encrypts or digests using a shared algorithm, and then sends this encrypted token back to your service for verification. Of course, you wouldn't want to publish that algorithm to github.
You cannot restrict a web service to apks signed by a specific key.
The signature of your apk gets validated by the Android OS, and is not directly shared with web services the device connects to. Even if you read your signature from the keystore and send it along with requests, an attacker could just send the same signature (e.g. the same byte stream) without having access to your private key. He would just need to grab your signed apk, read the bytes from the keystore (or listen to a legitimate request) and spoil the data.
You would need to sign individual requests to have a level of security. But if you keep a private key in release versions (the key not distributed on gitHub), and sign requests using that key you are not safe as the private key is distributed as part of your apk and thus can get extracted easily.
In any way your API could get accessed by other apks.
However, there might be another way to restrict your API, for example by using license tokens, etc. In that case you would probably not care if the user builds the apk by himself, as long as he has a valid license token. If a license token is exploited and distributed, you could react to a high amount of traffic on that license token and for example block it from further requests. As I don't use Google Play I'm not sure in how far they can get validated from your server, but maybe the application licensing portal is a good starting point to search for suitable tokens.