Introduction
Security has become a really big concern. If you are storing any user data on your servers, you should seriously consider encrypting all communications between your client and server using SSL. From past few years onwards mobile application increasing exponentially. Now a days Attackers/Hackers mainly focusing on the mobile application because of below three reasons
- lacking of good mobile standards
- developers are very new to mobile application development
- And more importantly using mobile applications, hackers can access important stuff like users calendars, contacts, browser histories, profile information, social streams, short messages or exact geographic locations.
As a security lover, I have analyzed more than 50 to 60 android applications in recent months. I have found many security vulnerabilities in security validation context.
In this article, I would like to explain few tips how to write security coding in android application.
Background
Lets start with Basics.
Before going reading this article please read Ranjan.D article (http://www.codeproject.com/Articles/818734/Article-Android-Connectivity). It will give brief idea about android connectivity.
you can open http connection using below code
URL urlConnection = new URL("http://www.codeproject.com/"); HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
But using http you can’t perform all operation like login page or passing username and password or credit card information. You need use HTTPS(more information regarding HTTPS.
What is HTTPS?
Hyper Text Transfer Protocol Secure (HTTPS) is a secure version of the Hyper Text Transfer Protocol (http). HTTPS allows secure ecommerce transactions, such as online banking.
Web browsers such as Internet Explorer and Firefox display a padlock icon to indicate that the website is secure, as it also displays https:// in the address bar
The simple different between https and http is in the https it will add another layer in the communication so it give extra security.
Https connection opening
URL urlConnection = new URL("https://www.codeproject.com/"); HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection(); InputStream in = new BufferedInputStream(urlConnection.getInputStream());
in this way we can open HTTPS connection and transfer data in securely.
HTTPS will use SSL/TLS mechanism to transfer data.
SSL/TLS:
SSL (Secure Sockets Layer) is a standard security technology for establishing an encrypted link between a server and a client—typically a web server (website) and a browser; or a mail server and a mail client (e.g., Outlook).
SSL allows sensitive information such as credit card numbers, social security numbers, and login credentials to be transmitted securely. Normally, data sent between browsers and web servers is sent in plain text—leaving you vulnerable to eavesdropping. If an attacker is able to intercept all data being sent between a browser and a web server they can see and use that information.
Overview of SSL/HTTPS and X.509 Certificates
If you don’t know anything about SSL or X.509 certificates, a cursory explanation might be helpful. Anyone who uses a self-signed certificate should understand how it differs from one that’s purchased, especially in regards to the potential security risks. So what is an SSL certificate? In general, two things: 1) a form of ID, comparable to a passport or driver’s license; and 2) a public encryption key, which can be used to encrypt data such that only the owner of the certificate can decrypt it. In other words, an SSL certificate serves two purposes: identify the site that is using the certificate and secure communications with it.
The next important concept to understand is that one certificate can be used to “sign” other certificates. In layman’s terms, Bob can use his certificate to put a “stamp of approval” on other certificates; if you trust Bob (and his certificate), you can trust any certificate that he’s signed. In this scenario, Bob is referred to as a “Certificate Authority“. All major browsers come with a collection of certificates from trusted Certificate Authorities (again, Thawte and Verisign being common examples).
The last step is to understand how the browser uses certificates. At a high level, here’s what happens when you open “https://www.yoursite.com” in a browser:
- The web server will send its certificate to the browser.
- The browser compares the “common name” in the certificate (sometimes called the “subject”) to the server’s domain name. For example, a certificate being sent from “www.yoursite.com” must have a common name of “www.yoursite.com” or the browser will issue a warning message saying it can’t be trusted.
- The browser tries to verify that it can trust the certificate. This is comparable to a bouncer checking your ID for a hologram that proves its authenticity. Just as anyone can make a fake ID and try to steal your identity, someone can also create a “forged” certificate with a common name of “www.yoursite.com” which appears to belong to your site. In an attempt to confirm that the certificate can be trusted, the browser checks to see if it has been signed by any of the certificates in its collection of trusted Certificate Authorities. If the browser can’t find trusted “stamp of approval” that matches the one on the certificate, so to speak, then it presents a warning message to the user saying the certificate can’t be trusted. Note that the user may choose to ignore the warning and accept the certificate, anyways.
- Once the certificate is verified (or if the user ignores warnings and tells the browser to accept it), the browser starts encrypting data using the public key in the certificate and sending that data to the server
Encryption in TLS (SSL)
Once the client verifies the authenticity of the server, it selects a shared key and encrypts it with the public key of the server (from the server’s certificate). It sends encrypted shared key to the server. The shared key can be decrypted only by using the private key of the server (asymmetric encryption), hence it is protected from eavesdropping. The server saves this shared key for the duration of the session. All subsequent communications in the session are encrypted and decrypted with this shared key (symmetric encryption).
Theory part is completed; Now let’s see few examples understand above theory clearly
If you open mail.live.com in your browser you can observe green icon symbol and https in your browser URL bar.
Click that green icon button and then select certificate information link.
You see below screen
This is the SSL certificate and you can find above certificate was issued to “mail.live.com” by Verisign.
Here Verisign is certificate authority and it is telling to your browser that you are connecting to mail.live.com web site. So browser ensures that browser established the secure connection between browser and webserver. In this way we can avoid man in the middle attack.
MITM Attack
In a MITM attack (MITMA), the attacker is in a position to intercept messages sent between communication partners.
In a passive MITMA, the attacker can only eavesdrop on the communication, and in an active MITMA, the attacker can also tamper with the communication (attacker label: Mallory). MITMAs against mobile devices are somewhat easier to execute than against traditional desktop computers, since the use of mobile devices frequently occurs in changing and untrusted environments. specifically, the use of open access points and the evil twin attack.
CA Providers
· Symantec (which bought VeriSign's SSL interests and owns Thawte and Geotrust) with 38.1% market share
· Comodo SSL with 29.1%
· Go Daddy with 13.4%
· GlobalSign with 10%
In Android Jelly bean version, you can find the trusted CA list. By navigating to
Settings -> Security -> Trusted credentials.
Https connection
URL url = new URL("https://www.example.com/"); HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection(); InputStream in = urlConnection.getInputStream();
This will work like charm if your connecting server (www.example.com) is having CA certificate.
But the problem will arise if your connecting server is using self-singed certificate.
What is self-signed certicates
A self signed certificate is a certificate that is signed by the person creating it rather than a trusted certificate authority
You can roughly divide SSL certificates into three types:
- Those issued by a certificate authority (CA) that is recognized by Android (e.g., VeriSign) or was issued by a downstream CA whose upstream CA is one recognized by Android
- Those issued by a CA that is not recognized by Android
- Self-signed certificates, whether used temporarily (e.g., during development) or in production
Android can only transparently handle the first set, where the root CA for the certificate is one recognized by Android.
Why people will use self-signed cert
- Self-signed certs are free of cost. If you need a cert which singed by trusted CA authorities you need pay around 100$ to 500$ per year.
- Self-singed cert is very common in mobile development (Because you’re mobile application will interact only one server unlike in the case of traditional web browsers).
- We can use same code in testing and Development people will use self-signed certificates.
During recent survey out of 8.1 million unique certificates, 3.2 million were browser trusted. The remaining 4.9 million untrusted certificates were a combination of self-signed certificates (48%), certificates signed by an unknown issuer (33%), and certificates signed by a known but untrusted issuer (19%).
Not only in this survey in my analysis I have also found that top 60% android applications are using self-singed certificate.
I personal feel that self-singed certificate are works great for mobile application because you no need buy CA cert and you can use same code for development and production (Note: if you’re using CA cert for production you need change your code).
But the trick here is common https code
URL url = new URL("https://www.example.com/"); HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection(); InputStream in = urlConnection.getInputStream();
If your using above code to validated self-singed cert, android application will throw error because self-singed is not validated by android OS. So you need write your own code to check the self-singed cert.
But in this area android developers making big mistakes. Self-singed cert is not common in web development and most of android developers are coming from web developer so developer has very less Knowledge about cryptography concepts.
During my analysis I saw that developers are simply copy pasting Stack Overflow and other blogs answers which will allow your app to trust all certificates by default. Even though most answers say that this should only be done in testing mode, but developers simply copying the code. This leads application vulnerability to man in the middle attacks and session hacking attacks.
examples:-
http://stackoverflow.com/questions/2012497/accepting-a-certificate-for-https-on-android?lq=1
http://www.caphal.com/android/using-self-signed-certificates-in-android/#toc_3
http://stackoverflow.com/questions/2642777/trusting-all-certificates-using-httpclient-over-https
Common mistakes in implementing self-singed certificates
Trusting all Certificates.
The primary responsibility of the TrustManager
is to determine whether the presented authentication credentials should be trusted. If the credentials are not trusted, the connection will be terminated. To authenticate the remote identity of a secure socket peer, you need to initialize an SSLContext
object with one or more TrustManager
s
import org.apache.http.conn.ssl.SSLSocketFactory; public class MySSLSocketFactory extends SSLSocketFactory { SSLContext sslContext = SSLContext.getInstance("TLS"); public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { super(truststore); TrustManager tm = new X509TrustManager() { public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } public X509Certificate[] getAcceptedIssuers() { return null; } }; sslContext.init(null, new TrustManager[] { tm }, null); } @Override public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); } @Override public Socket createSocket() throws IOException { return sslContext.getSocketFactory().createSocket(); } }
I have observed above code implementation in 20 to 25 applications out of 80 and 100 applications.
The above TrustManager interface can be implemented to trust all certificates, irrespective of who signed them or even for what subject they were issued. This interface would allow ANY certificate to be accepted. Accepting any certificate could endanger data integrity, security, etc.
In the above example checkClientTrusted, getAcceptedIssuers, checkServerTrusted are three important functions. Every developer should take care of implementation of these three functions. But few developers are just googling and copy those functions from different site.
Allowing all Hostnames.
It is possible to forgot checking whether the certificate was issued for this address or
Not, i. e., When accessing the server example.com, a certificate issued for some-other-domain.com is accepted.
HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER; DefaultHttpClient client = new DefaultHttpClient(); SchemeRegistry registry = new SchemeRegistry(); SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory(); socketFactory.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier); registry.register(new Scheme("https", socketFactory, 443)); SingleClientConnManager mgr = new SingleClientConnManager(client.getParams(), registry); DefaultHttpClient httpClient = new DefaultHttpClient(mgr, client.getParams()); // Set verifier HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier); // Example send http request final String url = "https://www.paypal.com” HttpPost httpPost = new HttpPost(url); HttpResponse response = httpClient.execute(httpPost); HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;
The above code will accept any CA certfificate issued to any domain. Which is a wrong implementation.
Mixed-Mode/No SSL.
App developers are free to mix secure and insecure connections in the same app or not use SSL at all. This is not directly an SSL issue, but it is relevant to mention that there are no outward signs and no possibility for a common app user to check whether a secure connection is being used. This opens the door for attacks such as SSL stripping or tools like Firesheep.
SSL stripping is another method by which a MITMA can be launched against an SSL connection, exploiting apps that use a mix of HTTP and HTTPS. SSL stripping relies on the fact that many SSL connections are established by clicking on a link in or being redirected from a non-SSL-protected site. During SSL stripping, Mallory replaces https:// links in the non-protected sites with insecure http:// links. Thus, unless the user notices that the links have been tampered with, Mallory can circumvent SSL protection altogether. This attack is mainly relevant to browser apps or apps using Android's WebView.
More info about SSL stripping
http://security.stackexchange.com/questions/41988/how-does-sslstrip-work
http://www.thoughtcrime.org/software/sslstrip/
Certificate pinning with self-singed certificate
It means hard-coding the certificate known to be used by the server in the mobile application. The app can then ignore the device’s trust store and rely on its own, and allow only SSL connections to hosts signed with certificates stored inside the application.
This also gives a possibility of trusting a host with a self-signed certificate without the need to install additional certificates on the device.
BENEFITS
- Increased security - with pinned SSL certificates, the app is independent of the device’s trust store. Compromising the hard coded trust store in the app is not so easy - the app would need to be decompiled, changed and then recompiled again - and it can’t be signed using the same Android keystore that the original developer of the app used.
- Reduced costs - SSL certificate pinning gives you the possibility to use a self-signed certificate that can be trusted. For example, you’re developing an app that uses your own API server. You can reduce the costs by using a self-signed certificate on your server (and pinning that certificate in your app) instead of paying for a certificate. Although a bit convoluted, this way, you've actually improved security and saved yourself some money.
DRAWBACKS
- Less flexibility - when you do SSL certificate pinning, changing the SSL certificate is not that easy. For every SSL certificate change, you have to make an update to the app, push it to Google Play and hope the users will install it.
In our case, the certificate was self-signed. This means that the default TrustManager in our SSLContext will not trust the server’s certificate, and the SSL connection will fail. To avoid this we will set up a custom TrustManager that trusts our self-signed certificate, and provide that TrustManager to our custom SSLContext.
We load the certificate into a KeyStore, use that KeyStore to produce an array of TrustManagers, and then use those TrustManagers to create the SSLContext.
In our app, we included the server certificate file in the application resources (since it doesn’t change from user to user, and doesn’t change very often), but you could put this in an external file as well
Implementation
Step 1: Create your self-signed certificate and create .bks file
1)
To create your BKS or key store you will need the file bcprov-jdk15on-146.jar, this class will do all the work for us, there is different versions but this one works for mehttp://www.bouncycastle.org/download/bcprov-jdk15on-146.jar also store this file into C:\codeproject.
Now you will use the Keytool (keytool comes with the Java SDK. You should find it in the directory that contains javac) to generate our keystore and to make sure that is working go to your cmd and type "Keytool", you will see the available commands which means is working, or you can access through "C:\Program Files (x86)\Java\jre7\bin>keytool".
2)
Use keytool to generate your key. It is in your Java bin path. In case, you already generated your keystore file, you can skip the first command.
keytool -genkey -alias codeproject -keystore C:\codeproject\codeprojectssl.keystore -validity 365
It creates a key with alias as code project, name of file as codeprojectssl.keystore. It will ask for various things like passwords for the key and the keystore, the SSL details. Please note that Common name will be your hostname, in our case it was: codeproject.com
3)
keytool -export -alias codeproject -keystore C:\codeproject\codeprojectssl.keystore -file C:\codeproject\codeprojectsslcert.cer
This command exports the key from .keystore file to .cer file.
4)
keytool -import -alias codeproject -file C:\codeproject\codeprojectsslcert.cer -keystore C:\codeproject\codeprojectssl.bks -storetype BKS -providerClass org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath C:\codeproject\bcprov-jdk15on-146.jar
Success! We have got the .bks file which we will put in our Android app, which will allow our app to communicate with our server which has the Self-signed SSL certificate.
Setp-2
We need to put our .keystore file in /androidappdir/res/raw/
Setp -3
We will write a new class that we will call as MyHttpClient which will extend DefaultHttpClient. This class will load our own trust store to check the SSL certificates, rather than android default trust store. Only when the certificate here matches on the server, will it work correctly. Here's how it looks like:
import java.io.InputStream; import java.security.KeyStore; import android.content.Context; public class MyHttpClient extends DefaultHttpClient { private static Context context; public static void setContext(Context context) { MyHttpClient.context = context; } public MyHttpClient(HttpParams params) { super(params); } public MyHttpClient(ClientConnectionManager httpConnectionManager, HttpParams params) { super(httpConnectionManager, params); } @Override protected ClientConnectionManager createClientConnectionManager() { SchemeRegistry registry = new SchemeRegistry(); registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); // Register for port 443 our SSLSocketFactory with our keystore // to the ConnectionManager registry.register(new Scheme("https", newSslSocketFactory(), 443)); return new SingleClientConnManager(getParams(), registry); } private SSLSocketFactory newSslSocketFactory() { try { // Get an instance of the Bouncy Castle KeyStore format KeyStore trusted = KeyStore.getInstance("BKS"); // Get the raw resource, which contains the keystore with // your trusted certificates (root and any intermediate certs) InputStream in = MyHttpClient.context.getResources().openRawResource(R.raw.codeprojectssl); //name of your keystore file here try { // Initialize the keystore with the provided trusted certificates // Provide the password of the keystore trusted.load(in, "YourKeystorePassword".toCharArray()); } finally { in.close(); } // Pass the keystore to the SSLSocketFactory. The factory is responsible // for the verification of the server certificate. SSLSocketFactory sf = new SSLSocketFactory(trusted); // Hostname verification from certificate // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506 sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER); // This can be changed to less stricter verifiers, according to need return sf; } catch (Exception e) { throw new AssertionError(e); } } }
you can call myhttpclient like this
// Instantiate the custom HttpClient DefaultHttpClient client = new MyHttpClient(getApplicationContext()); HttpGet get = new HttpGet("https://www.google.com"); // Execute the GET call and obtain the response HttpResponse getResponse = client.execute(get); HttpEntity responseEntity = getResponse.getEntity();
This is my first article in Codeproject.
Happy secure codeing .
Reference
http://www.thoughtcrime.org/blog/authenticity-is-broken-in-ssl-but-your-app-ha/
http://security.stackexchange.com/questions/29988/what-is-certificate-pinning
https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning
https://tools.ietf.org/html/draft-ietf-websec-key-pinning-20
https://media.blackhat.com/bh-us12/Turbo/Diquet/BH_US_12_Diqut_Osborne_Mobile_Certificate_Pinning_Slides.pdf
http://docs.oracle.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html#HowSSLWorks
转自:http://www.codeproject.com/Articles/826045/Android-security-Implementation-of-Self-signed-SSL