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)
Related
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();
}
}
}
I would like to ask the following. We have a mobile app both for Android & iOS that exchanges data with a .NET server.
For Android the ksoap2 library is used, while for iOS the Alamofire with AEXML libraries are used.
We would like to enable encryption for the communication between the server and the apps, specifically Message Security with Mutual Certificates (https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/message-security-with-mutual-certificates)
I am not able to find any information how either the Android or the iOS client could encrypt/decrypt the requests/responses.
Can you please provide any relative information?
Thanks in advance!
For the iOS Part.
By default, Alamofire will evaluate the certificate chain provided by the server using Apple's built in validation provided by the Security framework.
While this guarantees the certificate chain is valid, it does not prevent man-in-the-middle (MITM) attacks or other potential vulnerabilities.
In order to mitigate MITM attacks, applications dealing with sensitive customer data or financial information should use certificate or public key pinning provided by the ServerTrustPolicy.
ServerTrustPolicy
The ServerTrustPolicy enumeration evaluates the server trust generally provided by an URLAuthenticationChallenge when connecting to a server over a secure HTTPS connection.
let serverTrustPolicy = ServerTrustPolicy.pinCertificates(
certificates: ServerTrustPolicy.certificates(),
validateCertificateChain: true,
validateHost: true
)
There are many different cases of server trust evaluation giving you complete control over the validation process:
performDefaultEvaluation: Uses the default server trust evaluation while allowing you to control whether to validate the host provided by the challenge.
pinCertificates: Uses the pinned certificates to validate the server trust. The server trust is considered valid if one of the pinned certificates match one of the server certificates.
pinPublicKeys: Uses the pinned public keys to validate the server trust.
The server trust is considered valid if one of the pinned public keys match one of the server certificate public keys.
disableEvaluation: Disables all evaluation which in turn will always consider any server trust as valid.
customEvaluation: Uses the associated closure to evaluate the validity of the server trust thus giving you complete control over the validation process. Use with caution.
AlamoFire documentation
For Android part, i am not experienced with but i came across someone asking about the same thing and got an answer with
what you need to do is only to install your certificate into the webserver and call the webservice URL like https://my.webservice.url/ instead of http://my.webservice.url/.
If your certificate is a self-signed certificate, means you did not
bought it from a certificate authority, you will need to set the
SSLSocketFactory. You can check the project wiki on how to do that:
http://code.google.com/p/ksoap2-android/wiki/CodingTipsAndTricks#How_to_set_the_SSLSocketFactory_on_a_https_connection__in_order
Check Here.
This might be Helpful too
UPDATE: i've found this framework SOAPEEngine
this one.
Supports Basic, Digest and NTLM Authentication, WS-Security, Client side Certificate and custom security header.
you check its example for more clarifications too.
Message encryption with WCF is done through the WS-Security protocol, by setting the security attribute mode to Message. As you have undoubtedly realized by now, WS-Security is not exactly popular on the Android and iOS platforms, mostly due to it having been superseded by other technologies (like HTTPS,) so your choices in terms of existing libraries are not abundant. The fact that not even Microsoft-owned Xamarin supports it says a lot.
First, a word on WS-Security, This protocol provides three main means of enhancing message security:
Authentication through security tokens
Signing SOAP messages
Encryption of SOAP messages
So a conforming implementation should really provide all three of these functions, but we are mostly interested in encryption here, as from the question and comments it seems like you have the authentication part working.
Therefore, assuming we are looking for a mobile platform library providing minimal WCF compatibility with WS-Security signing and encryption:
Android
On Android, the closes to your needs is WSS-Client for Android. This project:
... implements the OASIS Web Service Security (WSS) standard for
Android platforms and makes XML Encryption and XML Signature available
for tablets and smartphones.
Note that this is GPL-licensed software. The readme says to contact the author for commercial license details. However, it seems to do what you're looking for. Once you have negotiated the key exchange with the server, to encrypt a previously constructed SOAP message using the SOAPUtil class you would do something like:
SOAPMessage message = SOAPUtil.createSOAPMessage();
//... Populate the message (possibly with another library)
SecCrypto serverCrypto = new SecCrypto(serverCertificate, null);
SecCrypto clientCrypto = new SecCrypto(clientPublicKey, clientPrivateKey);
SOAPMessage securedMessage = SOAPUtil.secureSOAPMessage(message, new HashMap<String,String>(SecConstants.REQ_ENCRYPT_SIGN, "yes"), clientCrypto, serverCrypto);
//...
SOAPMessage returnedSecuredMessage = SOAPUtil.sendSOAPMessage(context, securedMessage, endpoint, cryptoParams);
SOAPMessage returnedMessage = SOAPUtil.validateSOAPMessage(returnedSecuredMessage, new HashMap<String,String>(SecConstants.RES_DECRYPT_VERIFY, "yes", decCrypto);
Nevertheless, be prepared to do quite a bit of configuration work and debugging to make it match your server's needs.
If you are looking for a more current and actively developed product, Quasar Development offers a WS-Security implementation for Android.
iOS
Things look a lot more bleak on the iOS side. There are a few libraries claiming varying degrees of support for WSS, but none of them seem to match your needs:
At first SOAPEngine looks the most promising, as it claims
support for WS-Security. However, in a footnote it says that it has a
limitation that it only supports the WCF basicHttpBinding. This
would actually be OK if true. The binding used in the sample code you
linked to in the question is wsHttpBinding however it's important
to note that both wsHttpBinding and basicHttpBinding have support
for encryption though WS-Security. The difference is that
wsHttpBinding supports WS-Security by default (whereas it needs to
be enabled with basicHttpBinding) and it also supports
WS-ReliableMessaging and some other features you may or may not
care about. But the basicHttpBinding is the one intended for
compatibility with other technologies. So in order to have
WS-Security encryption on your WCF server and maximize compatibility
with other technologies at the same time, it would be OK to use
basicHttpBinding and enable WS-Security signing and encryption by
setting the mode security attribute to Message. With the
Message attribute, from the docs:
Security is provided using SOAP message security. By default, the body
is encrypted and signed. For this binding, the system requires that
the server certificate be provided to the client out of band. The only
valid ClientCredentialType for this binding is Certificate.
But this is of no use as SOAPEngine does not have any support for
encrypting messages (or at least I could not find any support for it
in the API). The only WS-Security function it supports is
authentication. So the claim that it supports WS-Security seems
misleading as the support is quite limited.
ServiceNow offers very limited support for WS-Security. It only
supports verifying server signatures. No encryption or signing on the
client side.
Chilkat has some rudimentary XML support and there is sample
code for WS-Security authentication. I didn't see any support or
sample code for WS-Security encryption.
Therefore for iOS, to the best of my knowledge, your two options are:
Pick one of the existing libraries that best matches your other needs
and reach out to the developer and see if you can get them to add the
WS-Security features you need.
Implement the bare minimum features
you need yourself. The spec is actually not that complicated and
there is sample code out there (for non-mobile platforms) that you
can use as guide, like WSS4J for example.
In Android:
I use kasoap2 to call the web services, but before the call, to enable mutual authentication with client certificate you need to initialize a SSLContext with the client authentication keys (KeyManager).
To do that you have to load your certificate and the corresponding password in a KeyStore object, my certificate is a *.pfx file. I cerate a "PKCS12" KeyStore instance. Then you need a KeyManagerFactory object to obtain the KeyManager array. I use a "PKIX" KeyManagerFactory instance. The KeyManager array is needed to init the SSLContext.
Here is an example:
public void enableMutualAuthentication(String filename, String password) {
try {
// InputStream to read the certificate file
FileInputStream cert = new FileInputStream(filename);
char[] pass = password.toCharArray();
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(cert ,pass);
cert.close();
KeyManagerFactory keymanagerfactory = javax.net.ssl.KeyManagerFactory.getInstance("PKIX");
keymanagerfactory.init(keystore, pass);
KeyManager[] keymanagers = keymanagerfactory.getKeyManagers();
// This is not for the mutual authentication.
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
} };
// Install the mutual authentication manager
// Install the all-trusting trust manager
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(keymanagers, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
// Create all-trusting host name verifier
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
// Install the all-trusting host verifier
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (UnrecoverableKeyException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
}
}
Check those links, was what help me most.
https://chariotsolutions.com/blog/post/https-with-client-certificates-on/
http://callistaenterprise.se/blogg/teknik/2011/11/24/android-tlsssl-mutual-authentication/
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();
}
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.
I have developed an android application which need secure communication with the server. I get exception about untrusted server because my server's certificate is not part of android's cert list.
I make use of following KeyChain APIs (available from ICS onwards) to prompt user for the certificate installation, after which the communication works seamlessly.
BufferedInputStream bis = new BufferedInputStream(getAssets().open(
PKCS12_FILENAME));
byte[] keychain = new byte[bis.available()];
bis.read(keychain);
Intent installIntent = KeyChain.createInstallIntent();
installIntent.putExtra(KeyChain.EXTRA_PKCS12, keychain);
installIntent.putExtra(KeyChain.EXTRA_NAME, DEFAULT_ALIAS);
startActivityForResult(installIntent, INSTALL_KEYCHAIN_CODE);
I am using the above code on the application startup and it prompts even when the certificate is already present. With regards to this, I have following two questions,
Programmatically how do I identify whether a particular certificate is already present or not? So that I prompt only when it is not already present.
Is there any event which occur during application installation, that I should use to prompt user for the certificate installation?
You can use something like this to enumerate trusted certificates. If the alias starts with 'user' it is user-installed. This is not part of the public API though, so it might break on future versions. More details here: http://nelenkov.blogspot.com/2011/12/ics-trust-store-implementation.html
KeyStore ks = KeyStore.getInstance("AndroidCAStore");
ks.load(null, null);
Enumeration aliases = ks.aliases();
while (aliases.hasMoreElements()) {
String alias = aliases.nextElement();
X09Certificate cert = (X509Certificate)
ks.getCertificate(alias);
Log.d(TAG, "Subject DN: " +
cert.getSubjectDN().getName());
Log.d(TAG, "Issuer DN: " +
cert.getIssuerDN().getName());
}
Since seems that it is not possible to retrieve a list of installed certificates programmatically, you can't say when your certificate is already installed. IMHO you should install it if you get the exception about untrusted server.
Also there is not an event occouring during application installation (also who should catch such event?)