Android Application Secure Design/Secure Coding Guidebook
Android Application Secure Design/Secure Coding Guidebook
http://www.jssec.org/dl/android_securecoding_en.pdf
Android Application
Secure Design/Secure Coding
Guidebook
- Secure communication! -
The content of this guide is up to date as of the time of publication, but standards and environments are constantly evolving.
When using sample code, make sure you are adhering to the latest coding standards and best practices.
JSSEC and the writers of this guide are not responsible for how you use this document. Full responsibility lies with you, the user
of the information provided.
Parts of this document are copied from or based on content created and provided by Google, Inc. They are used here in
accordance with the provisions of the Creative Commons Attribution 3.0 License
2.
3.
4.
5.
6.
Introduction ................................................................................................................................ 9
1.1.
1.2.
1.3.
2.2.
2.3.
2.4.
2.5.
3.2.
4.2.
4.3.
4.4.
4.5.
4.6.
4.7.
4.8.
4.9.
5.2.
5.3.
5.4.
Revision history
Date
2014-4-01
Revised contents
Initial English version
New versions of the guidebook updated based on public opinions and comments.
- Published by -
Member
Masaru Matsunami
Mayumi Nishiyama
BJIT Inc.
Masaki Kubo
Daniel Burrowes
Zachary Mathis
Renta Futamura
Ikuya Fukumoto
Hiroko Nakajima
Ken Okuyama
Satoshi Fujimura
Setsuko Kaji
Taeko Ito
Yoshinori Kataoka
Hidenori Yamaji
Koji Isoda
Michiyoshi Sato
Member
Masaru Matsunami
Masaomi Adachi
Yuki Abe
Youichi Higa
Ikuya Fukumoto
Kiyoshi Hata
Yuu Fukui
Eiji Hoshimoto
Shun Yokoi
Takeshi Fujiwara
Masaki Kubo
Shigenori Takei
Hiroshi Kumagai
Yozo Toda
Tohru Ohzono
Shigeru Yatabe
Mikiya Arai
Akira Ando
Ryohji Ikebe
Jun Ogiso
Ken Okuyama
Muneaki Nishimura
Yoshinori Kataoka
Koji Furusawa
Kenji Yamaoka
Gaku Taniguchi
Masaru Matsunami
Member
Shigenori Takei
Keisuke Takemori
KDDI CORPORATION
Toda
Takao Yamakawa
Naonobu Yatsukawa
Shigeki Fujii
UNIADEX, Ltd.
Kitamura
Masaru Matsunami
Member
Katsuhiko Sato
Shigenori Takei
Hoshimoto
Toda
Yoichi Taguchi
Masahiko Sakamoto
Michiyoshi Sato
Masakazu Hattori
Naonobu Yatsukawa
Masaaki Chida
NetAgent Inc.
Shigeki Fujii
UNIADEX, Ltd.
Kitamura
1. Introduction
1.1. Building a Secure Smartphone Society
This guidebook is a collection of tips concerning the know-how of secure designs and secure coding
for Android application developers. Our intent is to have as many Android application developers as
possible take advantage of this, and for that reason we are making it public.
In recent years, the smartphone market has witnessed a rapid expansion, and its momentum seems
unstoppable. Its accelerated growth is brought on due to the diverse range of applications. An
unspecified large number of key functions of mobile phones that were once not accessible due to
security restrictions on conventional mobile phones have been made open to smartphone
applications. Subsequently, the availability of varied applications that were once closed to
conventional mobile phones is what makes smartphones more attractive.
With great power that comes from smartphone applications comes great responsibility from their
developers. The default security restrictions on conventional mobile phones had made it possible to
maintain a relative level of security even for applications that were developed without security
awareness. As it has been aforementioned with regard to smartphones, since the key advantage of a
smartphone is that they are open to application developers, if the developers design or code their
applications without the knowledge of security issues then this could lead to risks of users' personal
information leakage or exploitation by malware causing financial damage such as from illicit calls to
premium-rate numbers.
Due to Android being a very open model allowing access to many functions on the smartphone, it is
believed that Android application developers need to take more care about security issues than iOS
application developers. In addition, responsibility for application security is almost solely left to the
application developers.
screening from a marketplace such as Google Play (former Android Market), though this is not
In conjunction with the rapid growth of the smartphone market, there has been a sudden influx of
software engineers from different areas in the smartphone application development market. As a
result, there is an urgent call for the sharing knowledge of secure design and consolidation of secure
coding know-how for specific security issues related to mobile applications.
Due to these circumstances, Japan's Smartphone Security Association (JSSEC) has launched the
Secure Coding Group, and by collecting the know-how of secure design as well as secure coding of
Android applications, it has decided to make all of the information public with this guidebook. It is
our intention to raise the security level of many of the Android applications that are released in the
market by having many Android application developers become acquainted with the know-how of
secure design and coding. As a result, we believe we will be contributing to the creation of a more
as accurate as possible, but we cannot make any guarantees. We believe it is our priority to publicize
and share the know-how in a timely fashion. Equally, we will upload and publicize what we consider
to be the latest and most accurate correct information at that particular juncture, and will update it
with more accurate information once we receive any feedback or corrections. In other words, we are
taking the beta version approach on a regular basis. We think this approach would be meaningful for
many of the Android application developers who are planning on using the Guidebook.
The latest version of the Guidebook and sample codes can be obtained from the URL below.
http://www.jssec.org/dl/android_securecoding_en.pdf
http://www.jssec.org/dl/android_securecoding_en.zip
Guidebook (English)
The latest Japanese version can be obtained from the URL below.
10
http://www.jssec.org/dl/android_securecoding.pdf
http://www.jssec.org/dl/android_securecoding.zip
Guidebook (Japanese)
The information contained in the Guidebook may be inaccurate. Please use the information
In case of finding any mistakes contained in the Guidebook, please send us an e-mail to the
address listed below. However, we cannot guarantee a reply or any revisions thereof.
Content:
Subject:
11
practices and their suggested revisions. Although this approach can be useful at the time of
reviewing the source code that has already been coded, it can be confusing for developers that are
about to start coding, as they do not know which article to refer to.
The Guidebook has focused on the developer's context of "What is a developer trying to do at this
moment?" Equally, we have taken steps to prepare articles that are aligned with the developer's
context. For example, we have divided articles into project units by presuming that a developer will
be involved in operations such as [Creating/Using Activities], [Using SQLite], etc.
We believe that by publishing articles that support the developer's context, developers will be able to
easily locate necessary articles that will be instantly useful in their projects.
12
in a hurry, please look up the Sample Code and Rule Book sections. The content is provided in a way
where it can be reused to a certain degree. For those who have issues that go beyond these, please
refer the Advanced Topics section. We have given descriptions that will be helpful in finding solutions
for individual cases.
Unless it is specifically noted, our focus of development will be targeted to platforms concerning
Android 2.2 (API Level 8) and later. Since we have not verified the operational capability of any
versions pertaining to Android 2.1 (API Level 7) or prior, the measures described may prove
ineffective on these older systems. In addition, even for versions that are covered under the scope of
focus, it is important to verify their operational capability by testing them on your own environment
before releasing them publically.
2.2.1. Sample Code
Sample code that serves as the basic model within the developer's context and functions as the
theme of an article is published in the Sample Code section. If there are multiple patterns, we have
provided source code for the different patterns and classified them accordingly. We have strived to
make our commentaries as simple as possible. For example, when we want to direct the reader's
attention to a security issue that requires attention, a bullet-point number will appear next to "Point"
in the article. We will also comment on the sample code that corresponds to the bullet-point number
by writing "*** Point (Number) ***." Please note that a single point may correspond to multiple
pieces of sample code. There are sections throughout the entire source code, albeit very little
compared to the entire code, that requires our attention for security. In order to be able to survey the
sections that call for scrutiny, we try to post the entire class unit of sample code.
Please note that only a portion of sample code is posted in the Guidebook. A compressed file, which
contains the entire sample code, is made public in the URL listed below. It is made public by the
Apache License, Version 2.0; therefore, please feel free to copy and paste it. Please note that we have
minimized the code for error processing in the sample code to prevent it from becoming too long.
http://www.jssec.org/dl/android_securecoding_en.zip
The projects/keystore file that is attached in the sample code is the keystore file that contains the
developer key for the signature of the APK. The password is "android." Please use it when singing the
APK in the In-house sample code.
We have provided the keystore file, debug.keystore, for debugging purposes. When using Eclipse for
development, it is convenient for verifying the operational capability of the In-house sample code if a
file path is set in the In-house defined debug keystore of [Android]-[Build] of [Preferences] in
advance. In addition, for sample code that is comprised of multiple APKs, it is necessary to match the
13
cooperation between each APK. If the android:debuggable setting is not explicit set when installing
the APK from Eclipse, it will automatically become android:debuggable= "true."
For embedding the sample code as well as keystore file into Eclipse, please refer to "2.5 Steps to
Install Sample Codes into Eclipse."
be published in the Rule Book section. Rules to be handled in that section will be listed in a table
format at the beginning and will be divided into two levels: "Required" and "Recommended." The
rules will consist of two types of affirmative and negative statements. For example, an affirmative
statement that expresses that a rule is required will say "Required." An affirmative statement that
expresses a recommendation will say "Recommended." For a negative statement that expresses the
requisite nature of the rule would say, "Definitely not do." For a negative sentence that expresses a
recommendation would say, "Not recommended." Since these differentiations of levels are based on
the subjective viewpoint of the author, it should only be used as a point of reference.
Sample code that is posted in the Sample Code section reflect these rules and matters that need to be
considered, and a detailed explanation on them is available in the Rule Book section. Furthermore,
rules and matters that need to be considered that are not dealt with in the Sample Code section are
handled in the Rule Book section.
2.2.3. Advanced Topics
Items that require our attention, but that could not be covered in the Sample Code and Rule Book
sections within the developer's context will be published in the Advanced Topics section. The
Advanced Topics section can be utilized to explore ways to solve separate issues that could not be
solved in the Sample Code or Rule Book sections. For example, subject matters that contain personal
opinions as well as topics on the limitations of Android OS in relation the developer's context will be
Developers are always busy. Many developers are asked to have basic knowledge of security and
produce as many Android applications as quickly as possible in a somewhat safe manner than to
really understand the deep security matters. However, there are certain applications out there that
require a high level of security design and implementation from the beginning. For developers of
such applications, it is necessary for them to have a deep understanding concerning the security of
Android OS.
In order to benefit both developers who emphasize development speed and also those who
emphasize security, all articles of the Guidebook are divided into the three sections of Sample Code,
Rule Book, and Advanced Topics. The aim of the Sample Code and Rule Book sections is to provide
generalizations about security that anyone can benefit from and source code that will work with a
minimal amount of customization and hopefully by just copying and pasting. In the Advanced Topics
section, we offer materials that will help developers think in a certain way when they are facing
14
specific problems. It is the aim of the Advanced Topics section to help developers examine optimal
secure design and coding when they are involved in building individual applications.
15
"Application Security" section in figure below) for the development of Android applications that are
distributed primarily in a public market.
Application
Security
Device
Security
Figure 2.3-1
Security regarding the implementation of components in the Device Security of the above figure is
outside the scope of this guidebook. There are differences in the viewpoint of security between
general applications that are installed by users and pre-installed applications by device
manufacturers. The Guidebook only handles the former and does not deal with the latter. In the
current version, tips only on the implementation by Java are posted, but in future versions, we plan
on posting tips on JNI implementations as well.
Also as of now we do not handle threats that results from an attacker obtaining root privileges. We
will assume the premise of a secure Android device in which it is not possible to obtain root privileges
and base our security advice on utilizing the Android OS security model. For handling of assets and
threats, we have provided a detailed description on "3.1.3 Asset Classification and Protective
Countermeasures."
16
you read the literature mentioned below in conjunction with the Guidebook.
Authors: Fred Long, Dhruv Mohindra, Robert C. Seacord, Dean F. Sutherland, David Svoboda
http://www.amazon.com/dp/0321803957
17
projects depending on the purpose. Installing all of the sample code at once is described in, "2.5.1
Installing the Sample Project" and how to selectively install the sample code is described in, "2.5.2
Install by Selecting Individual Projects of the Sample." After the installation is completed, please refer
to "2.5.3 Setup the Sample Code Validator debug.keystore" and install the debug.keystore file into
Eclipse. We have verified the following steps in the following environment:
OS
Android SDK
Android 2.2(API 8)
For any sample projects that do not require any attention can be built through Android
2.2 (API 8). However, depending on the sample, some may require SDK Platform that
comes later than Android 2.3.3 (API 10).
2.
Acquire the sample code from the URL shown in "2.2.1 Sample Code"
Right click on the sample code that has been compressed into zip file, and click on "Extract All"
as shown below.
Figure 2.5-1
3.
18
Figure 2.5-2
After
clicking
on
the
"Extract"
button,
right
underneath
"C:"
folder
called
Figure 2.5-3
The sample code is contained in the"android_securecoding" folder. For example, when you want
to refer to the sample code within "4.1.1.3 Creating/Using Partner Activities" of "4.1
Activity PartnerActivity
19
In this way, the sample code project will be located under the chapter title in the
"android_securecoding" folder.
4.
Launch Eclipse from the start menu or from a desktop icon. From the displayed selected dialogue,
designate "C:android_securecoding" the workspace that was deployed in the previous step. If
the selected dialogue is not displayed, click on "File->Switch Workspace->Other..." from the
menu.
C:android_securecoding
Figure 2.5-4
20
5.
Close the Android IDE pane after Eclipse has been launched.
Figure 2.5-5
21
6.
Start importing
Figure 2.5-6
7.
Figure 2.5-7
22
8.
Figure 2.5-8
23
9.
Figure 2.5-9
24
Click "Finish" to import all of the projects. If "Copy project into workspace" is checked, the folder
hierarchy of each project will become flat. Please be aware that some projects may refer to the
"Shared/JSSEC Shared" folder which can lead to a compilation error if the folder hierarchy is
changed. In the case of installing only a portion of a project, please refer to, "2.5.2 Install by
Selecting Individual Projects of the Sample."
Figure 2.5-10
25
Figure 2.5-11
26
27
Some projects may refer to the "JSSEC Shared" folder. In that case, if the "JSSEC Shared" is not
imported, it will look like the figure below. Please import "JSSEC Shared" if you need it
Figure 2.5-13
28
Figure 2.5-14
29
device or emulator. Install the debugging key file "debug.keystore" that will be used for the signature
into Eclipse.
1.
Click on Window->Preferences
Figure 2.5-15
30
2.
Figure 2.5-16
31
3.
Figure 2.5-17
32
4.
Finally, after verifying that the path of debug.keystore, click on "OK" to finish as shown below.
Figure 2.5-18
33
development, this chapter will deal with the basic knowledge on general secure design and secure
coding of Android smartphones and tablets. Since we will be referring to secure design and coding
concepts in the later chapters we recommend that you familiarize yourself with the content contained
in this chapter first.
or applications. First, we need to have a grasp over the objects we want to protect. We will call these
assets. Next, we want to gain an understanding over the possible attacks that can take place on an
asset. We will call these threats. Finally, we will examine and implement measures to protect assets
from the various threats. We will call these countermeasures.
What we mean by countermeasures here is secure design and secure coding, and will deal with these
subjects after Chapter 4. In this section, we will focus on explaining assets and threats.
3.1.1. Asset: Object of Protection
There are two types of objects of protection within a system or an application: information and
functions. We will call these information assets and function assets. An information asset refers to
the type of information that can be referred to or changed only by people who have permission. It is
a type of information that cannot be referred to or changed by anyone who does not have the
permission. A function asset refers to a function that can be used only by people who have
permission and no one else.
Below, we will introduce types of information assets and functional assets that exist in Android
smartphones and tablets. We would like you to use the following as a point of reference to deliberate
on matters with regard to assets when developing a system that utilizes Android applications or
Android smartphones/tablets. For the sake of simplicity, we will collectively call Android
smartphones/tablets as Android smartphones.
34
Phone number
Call history
IMEI
IMSI
Sensor information
Various setup
information
Account information
Media data
Remarks
...
Table 3.1-2 Examples of Information Managed by an Application
Information
Contacts
E-mail address
E-mail mail box
Web bookmarks
Web browsing history
Calendar
Facebook
Twitter
Remarks
Contacts of acquaintances
User's e-mail address
Content of incoming and outgoing e-mail, attachments, etc.
Bookmarks
Browsing history
Plans, to-do list, events, etc.
SNS content, etc.
SNS content, etc.
...
The type of information seen in Table 3.1-1 is mainly the type of information that is stored on the
Android smartphone itself or on an SD card. Similarly, the type of information seen in Table 3.1-2 is
primarily managed by an application. In particular, the type of information seen in Table 3.1-2 grows
in proportion to the number of applications installed on the device.
Table 3.1-3 is the amount of information contained in one entry case of contacts. The information
here is not of the smartphone user's, but of the smartphone user's friends. In other words, we must
be aware that a smartphone not only contains information on the user, but of other people too.
Table 3.1-3 Examples of Information Contained in One Contact Entry
Information
Content
Phone number
Photo
E-mail address
IM address
Nicknames
Address
Group membership
Website
Events
AIM, MSN, Yahoo, Skype, QQ, Google Talk, ICQ, Jabber, Net meeting, etc.
Acronyms, initials, maiden names, nicknames, etc.
35
Relation
SIP address
...
Until now, we have primarily focused on information about smartphone users, however, application
possesses other important information as well. Figure 3.1-1 displays a typical view of the
information inside an application divided into the program portion and data portion. The program
portion mainly consists of information about the application developer, and the data portion mostly
pertains to user information. Since there could be information that an application developer may not
want a user to have access to, it is important to provide protective countermeasures to prohibit a user
from referring to or making changes to such information.
Picture Manager
Users Information
Program
Data
/data/app/com.sonydna.picturemanager.apk
AndroidManifest.xml
classes.dex
Java Code (Binary)
resources.arsc
Resources (e.g. Strings)
assets
AppAbout_en.html
Bundled Data
res
drawable-hdpi
broken_image.png
Image Files
layout
about.xml
Layout Information
xml
setting.xml
XML Files
/data/data/com.sonydna.picturemanager
cache
webviewCache
Cache of WebView
databases
label.db
DB for Application
metadata.db
webview.db
DB for WebView
webviewCache.db
DB for WebView Cache
files
MediaList1.dat
Application Data Files
lib
shared_prefs
Preference File
com.sonydna.picturemanager_preferences.xml
features are exploited by a malware, etc., damages in the form of unexpected charges or loss of
privacy may be incurred by a user. Therefore, appropriate protective counter-measures that are
equal the one extended to information asset should be set in place.
36
Function
Camera
Calling
Volume
Network communication
GPS
Bluetooth communication
NFC communication
...
...
In addition to the functions that the Android OS provides to an application, the inter-application
communication components of Android applications are included as part of the function assets as
well. Android applications can allow other applications to utilize features by accessing their internal
components. We call this inter-application communication. This is a convenient feature, however,
there have been instances where access to functions that should only be used inside a particular
application are mistakenly given to other applications due the lack of knowledge regarding secure
coding on the part of the developer. There are functions provided by the application that could be
exploited by malware that resides locally on the device. Therefore, it is necessary to have appropriate
protective countermeasures to only allow legitimate applications to access these functions.
3.1.2. Threats: Attacks that Threaten Assets
In the previous section, we talked about the assets of an Android smartphone. In this section, we will
explain about attacks that can threaten an asset. Put simply, a threat to an asset is when a third party
who should not have permission, accesses, changes, deletes or creates an information asset or
illicitly uses a function asset. The act of directly or indirectly attacking such assets is called a "threat."
Furthermore, the malicious person or applications that commit these acts are referred to as the
source of the threats. Malicious attackers and malware are the sources of threats but are not the
threats themselves. The relationship between our definitions of assets, threats, threat sources,
vulnerabilities, and damage are shown below in Figure 3.1-2.
37
http://www.jssec.org/dl/android_securecoding_en.pdf
Application
Threat(Attack)
Assets
Threat(Attack)
Impact
Vulnerability
Threat Source
Figure 3.1-2 Relation between Asset, Threat, Threat Source, Vulnerability, and Damage
Figure 3.1-3 shows a typical environment that an Android application behaves in. From now on, in
order to expand on the explanation concerning the type of threats an Android application faces by
using this figure as a base, we will first learn how to view this figure.
Smartphone Security Area
Server
Smartphone
Application
3G/4G/Wi-Fi
One user
Information
Web
Service
All users
information
within a smartphone, we are only showing a single application in the figure in order to explain the
threats clearly. Smartphone-based applications mainly handle user information, but the
server-based web services collectively manage information of all of its users. Consequently, there is
no change the importance of server security as usual. We will not touch upon issues relating to server
security as it falls outside of the scope of the Guidebook.
38
http://www.jssec.org/dl/android_securecoding_en.pdf
We will use the following figure to describe the type of threats that exist towards Android
applications.
Server
Smartphone
Attack
App
Attack
Web
Service
Information
of all users
will move between the networks connecting them. As indicated in Figure 3.1-4, a network-based
malicious third party may access (sniff) any information during this communication or try to change
information (data manipulation). The malicious attacker in the middle (also referred to as "Man in The
Middle") can also pretend to be the real server tricking the application. Without saying,
network-based malicious third parties will usually try to attack the server as well.
39
http://www.jssec.org/dl/android_securecoding_en.pdf
3.1.2.2.
Server
Smartphone
Market
Malware
Attack
Web
Service
App
Information
of all users
Careless
user
market in order to expand on its features. The downside to users being able to freely install many
applications is that they will sometimes mistakenly install malware. As shown in Figure 3.1-5,
Server
Smartphone
App
Web
Service
Passive
attack
Careless
user
SD
Attack
file
Information
of all users
Figure 3.1-6 Attack from Malicious Files that Exploit a Vulnerability in an Application
Various types of files such as music, images, videos and documents are widely available on the
40
http://www.jssec.org/dl/android_securecoding_en.pdf
Internet and typically users will download many files to their SD card in order to use them on their
smartphone. Furthermore, it is also common to download attached files sent in an e-mail. These files
If there is any vulnerability in the function of an application that processes these files, an attacker can
use a malicious file to exploit it and gain access to information or function assets of the application.
In particular, vulnerabilities are often present in processing a file format with a complex data
structure. The attacker can fulfill many different goals when exploiting an application in this way.
As shown in Figure 3.1-6, an attack file stays dormant until it is opened by a vulnerable application.
Once it is opened, it will start causing havoc by taking advantage of an application's vulnerability. In
Server
Smartphone
Web
Service
App
Attack
adb
debug
USB
Smartphone
malicious user
Information
of all users
features that help to develop and analyze an application are openly provided to the general user.
Among the features that are provided, the useful ADB debugging feature can be accessed by anyone
without registration or screening. This feature allows an Android smartphone user to easily perform
OS or application analysis.
As it is shown in Figure 3.1-7, a smartphone user with malicious intent can analyze an application by
taking advantage of the debugging feature of ADB and try to gain access to information or function
assets of an application. If the actual asset contained in the application belongs to the user, it poses
no problem, but if the asset belongs to someone other than the user, such as the application
developer, then it will become a concern. Accordingly, we need to be aware that the legitimate
41
Malicious attacker
standing by smartphone
Attack
BT
Smartphone
Server
Attack
Web
Service
App
Information
of all users
Figure 3.1-8 Attacks from a Malicious Third Party in the Proximity of a Smartphone
Due to face that most smartphones possess a variety of near-field communication mechanisms, such
as NFC, Bluetooth and Wifi, we must not forget that attacks can occur from a malicious attacker who
is in physical proximity of a smartphone. An attacker can shoulder surf a password while peeping
over a user who is inputting it in. Or, as indicated in Figure 3.1-8, an attacker can be more
sophisticated and attack the Bluetooth functionality of an application from a remote distance. There
is also the threat that a malicious person could steal the smartphone creating a risk of data leakage
or even destroy the smartphone causing a loss of critical information. Developers need to take these
risks into consideration as well as early as the design stage.
3.1.2.6. Summary of Threats
Smartphone Security Area
Malicious attacker
standing by smartphone
Attack
BT
Smartphone
Market
Careless
user
42
Malware
Attack
Passive
attack
SD
Attack
file
Attack
App
Server
Attack
Attack
adb
debug
Attack
USB
Smartphone
malicious user
Web
Service
Information
of all users
are surrounded by a wide variety of threats and the figure above does not include all of them.
Through our daily information gathering, we need to spread the awareness concerning the various
threats that surround an Android application and be aware of them during the application's secure
design and coding. The following literature that was created by Japan's Smartphone Security
Association (JSSEC) contains other valuable information on the threats to smartphone security.
http://www.jssec.org/dl/Guidebook2012Enew_v1.0.pdf (English)
http://www.jssec.org/dl/NetworkSecurityGuide1.pdf (Japanese)
http://www.jssec.org/dl/cloudguide2012_beta.pdf (Japanese)
http://www.jssec.org/dl/MDMGuideV1.pdf (Japanese)
43
44
Low
Level of Protective
Counter-Measures
The amount of damage the asset Provide
protection
against
causes is fatal and catastrophic to sophisticated attacks that break
the organization or an individual's through the Android OS security
model and prevent root privilege
activity.
i.e.) When an asset at this level is
compromises and attacks that alter
the dex portion of an APK.
damaged, the organization
will not be able to continue its Ensure security takes priority over
business.
other elements such as user
experience, etc.
The amount of damage the asset
causes has a substantial impact
the organization or an individual's
activity.
i.e.) When an asset at this level is
damaged, the organization's
profit level deteriorates,
adversely affecting its
business.
The amount of damage the asset
causes has a limited impact on
the organization or an individual's
activity.
I.e.) When an asset at this level is
damaged, the organization's
profit level will be affected
but is able to compensate its
losses from other resources.
Medium
Asset Level
Asset classification and protective countermeasures described in the Guidebook are proposed under
the premise of a secure Android device where root privilege has not been compromised. Furthermore,
it is based on the security measures that utilize the security model of Android OS. Specifically, we are
hypothetically devising protective countermeasures by utilizing the Android OS security model on the
premise of a functioning Android OS security model against assets that are classified lower than or
equal to the medium level asset. On the other hand, we also believe in the necessity of protecting
high level assets from attacks that are caused due the breaching of the Android OS security model.
Such attacks include the compromise of root privileges and attacks that analyze or alter the APK
binary. To protect these types of assets, we need to design sophisticated defensive countermeasures
against such threats through the combination of multiple methods such as encryption, obfuscation,
hardware support and server support. As the collection of know-how regarding these defenses
cannot be easily written in this guidebook, and since appropriate defensive design differ in
scope. We recommend that you consult with a security specialist who is well versed in tamper
resistant designs of Android if your device requires protection from sophisticated attacks that
include attacks resulting from the compromise of root privileges or attacks caused by the analysis or
alteration of an APK file.
45
level and the level of protective countermeasures for each information asset that an application
handles.
46
From the viewpoint of (a), "urlstr is the correct URL", verified through the non-occurrence of a
MalformedURLException by a new URL(). However, this is not sufficient. Furthermore, when a
"file://..." formatted URL is designated by urlstr, the file of the internal file system is opened and is
displayed in TextView rather than the remote web page. This does not fulfill the viewpoint of (b),
since it does not guarantee the behavior which was expected by the programmer.
The next example shows a revision to fix the security bugs. Through the viewpoint of (a), the input
data is validated by checking that "urlstr is a legitimate URL and the protocol is limited to http or
https." As a result, even by the viewpoint of (b), the acquisition of an Internet-routed InputStream is
guaranteed through url.openConnection().getInputStream().
47
Revised sample code that displays HTML of Internet-based Web page in TextView
TextView tv = (TextView) findViewById(R.id.textview);
InputStreamReader isr = null;
char[] text = new char[1024];
int read;
try {
String urlstr = getIntent().getStringExtra("WEBPAGE_URL");
URL url = new URL(urlstr);
String prot = url.getProtocol();
if (!"http".equals(prot) && !"https".equals(prot)) {
throw new MalformedURLException("invalid protocol");
}
isr = new InputStreamReader(url.openConnection().getInputStream());
while ((read=isr.read(text)) != -1) {
tv.append(new String(text, 0, read));
}
} catch (MalformedURLException e) { ...
Validating the safety of input data is called "Input Validation" and it is a fundamental secure coding
method. Surmising from the sense of the word of Input Validation, it is quite often the case where the
viewpoint of (a) is heeded but the viewpoint of (b) is forgotten. It is important to remember that
damage does not take place when data enters the program but when the program uses that data in
an incorrect way. We hope that you will refer the URLs listed below.
https://www.securecoding.cert.org/confluence/x/Ux (English)
Application of CERT Oracle Secure Coding Standard for Android Application Development
https://www.securecoding.cert.org/confluence/x/C4AiBw (English)
https://www.securecoding.cert.org/confluence/x/H4ClBg (English)
http://www.ipa.go.jp/security/awareness/vendor/programmingv2/clanguage.html (Japanese)
48
http://www.jssec.org/dl/android_securecoding_en.pdf
different security issues regarding each technology when designing and coding, then unexpected
vulnerabilities may arise. This chapter will explain about the different scenarios that developers will
need to know when using their application components.
4.1. Creating/Using Activities
4.1.1. Sample Code
The risks and countermeasures of using Activities differ depending on how that Activity is being used.
In this section, we have classified 4 types of Activities based on how the Activity is being used. You
can find out which type of activity you are supposed to create through the following chart shown
below. Since the secure coding best practice varies according to how the activity is used, we will also
explain about the implementation of the Activity as well.
Definition
Private Activity
Public Activity
Partner Activity
In-house Activity
Start
Yes
No
Use only in
the same application?
Yes
Use only
No
Yes
Private Activity
Public Activity
Partner Activity
No
In-house Activity
Figure 4.1-1
4.1 Creating/Using Activities
49
When using Activities that are only used within the application (Private Activity), as long as you use
explicit Intents to the class then you do not have to worry about accidently sending it to any other
application. However, there is a risk that a third party application can read an Intent that is used to
start the Activity. Therefore it is necessary to make sure that if you are putting sensitive information
inside an Intent used to start an Activity that you take countermeasures to make sure that it cannot
be read by a malicious third party.
2.
3.
4.
Handle the received intent carefully and securely, even though the intent was sent from the same
5.
Sensitive information can be sent since it is sending and receiving all within the same application.
application.
To make the Activity private, set the "exported" attribute of the Activity element in the
AndroidManifest.xml to false.
AndroidManifest.xml
50
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
PrivateActivity.java
package org.jssec.android.activity.privateactivity;
import
import
import
import
import
android.app.Activity;
android.content.Intent;
android.os.Bundle;
android.view.View;
android.widget.Toast;
51
Next, we show the sample code for how to use the Private Activity.
Point (Using an Activity):
6.
8.
Sensitive information can be sent since the destination activity is in the same application. 1
7.
Use the explicit Intents with the class specified to call an activity in the same application.
9.
Handle the received result data carefully and securely, even though the data comes from an
activity within the same application.
PrivateUserActivity.java
package org.jssec.android.activity.privateactivity;
import
import
import
import
import
android.app.Activity;
android.content.Intent;
android.os.Bundle;
android.view.View;
android.widget.Toast;
Caution: Unless points 1, 2 and 6 are abided by, there is a risk that Intents may be read by a third party.
Please refer to sections 4.1.2.2 and 4.1.2.3 for more details.
52
// *** POINT 9 *** Handle the received data carefully and securely,
// even though the data comes from an activity within the same application.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
Toast.makeText(this, String.format("Received result: "%s"", result), Toast.LENGTH_LONG).show();
break;
}
}
}
53
In addition, when using Public Activities, it is necessary to be aware of the fact that malware can also
receive or read the Intents sent to them.
2.
AndroidManifest.xml
PublicActivity.java
package org.jssec.android.activity.publicactivity;
import
import
import
import
import
android.app.Activity;
android.content.Intent;
android.os.Bundle;
android.view.View;
android.widget.Toast;
54
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// *** POINT 1 *** Handle the received intent carefully and securely.
// Since this is a public activity, it is possible that the sending application may be malware.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
String param = getIntent().getStringExtra("PARAM");
Toast.makeText(this, String.format("Received param: "%s"", param), Toast.LENGTH_LONG).show();
}
public void onReturnResultClick(View view) {
// *** POINT 2 *** When returning a result, do not include sensitive information.
// Since this is a public activity, it is possible that the receiving application may be malware.
// If there is no problem if the data gets received by malware, then it can be returned as a result.
Intent intent = new Intent();
intent.putExtra("RESULT", "Not Sensitive Info");
setResult(RESULT_OK, intent);
finish();
}
}
55
4.
PublicUserActivity.java
package org.jssec.android.activity.publicuser;
import
import
import
import
import
import
android.app.Activity;
android.content.ActivityNotFoundException;
android.content.Intent;
android.os.Bundle;
android.view.View;
android.widget.Toast;
56
There is a risk that a third party application can read an Intent that is used to start the Activity.
Therefore it is necessary to make sure that if you are putting sensitive information inside an Intent
used to start an Activity that you take countermeasures to make sure that it cannot be read by a
malicious third party
3.
5.
Handle the received intent carefully and securely, even though the intent was sent from a partner
2.
4.
6.
application.
Plesese refer to "4.1.3.2 Validating the Requesting Application" for how to validate an application by
a white list. Also, please refer to "5.2.1.3 How to verify the hash value of an application's certificate"
for how to verify the certificate hash value of a destination application which is specified in the
whitelist.
AndroidManifest.xml
57
PartnerActivity.java
package org.jssec.android.activity.partneractivity;
import org.jssec.android.shared.PkgCertWhitelists;
import org.jssec.android.shared.Utils;
import
import
import
import
import
import
android.app.Activity;
android.content.Context;
android.content.Intent;
android.os.Bundle;
android.view.View;
android.widget.Toast;
58
PkgCertWhitelists.java
package org.jssec.android.shared;
import java.util.HashMap;
import java.util.Map;
import android.content.Context;
public class PkgCertWhitelists {
private Map<String, String> mWhitelists = new HashMap<String, String>();
public boolean add(String pkgname, String sha256) {
if (pkgname == null) return false;
if (sha256 == null) return false;
sha256 = sha256.replaceAll(" ", "");
if (sha256.length() != 64) return false;
// SHA-256 -> 32 bytes -> 64 chars
sha256 = sha256.toUpperCase();
if (sha256.replaceAll("[0-9A-F]+", "").length() != 0) return false; // found non hex char
mWhitelists.put(pkgname, sha256);
return true;
}
public boolean test(Context ctx, String pkgname) {
// Get the correct hash value which corresponds to pkgname.
String correctHash = mWhitelists.get(pkgname);
// Compare the actual hash value of pkgname with the correct hash value.
return PkgCert.test(ctx, pkgname, correctHash);
}
}
PkgCert.java
package org.jssec.android.shared;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import
import
import
import
import
android.content.Context;
android.content.pm.PackageInfo;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
android.content.pm.Signature;
59
60
8.
9.
Verify if the certificate of the target application has been registered in a whitelist.
Do not set the FLAG_ACTIVITY_NEW_TASK flag for the intent that start an activity.
12. Handle the received result data carefully and securely, even though the data comes from a
partner application.
Refer to "4.1.3.2 Validating the Requesting Application" for how to validate applications by white list.
Also please refer to "5.2.1.3 How to verify the hash value of an application's certificate" for how to
verify the certificate hash value of a destination application which is to be specified in a white list.
AndroidManifest.xml
PartnerUserActivity.java
package org.jssec.android.activity.partneruser;
import org.jssec.android.shared.PkgCertWhitelists;
import org.jssec.android.shared.Utils;
import
import
import
import
import
import
import
android.app.Activity;
android.content.ActivityNotFoundException;
android.content.Context;
android.content.Intent;
android.os.Bundle;
android.view.View;
android.widget.Toast;
61
62
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != RESULT_OK) return;
switch (requestCode) {
case REQUEST_CODE:
String result = data.getStringExtra("RESULT");
// *** POINT 12 *** Handle the received data carefully and securely,
// even though the data comes from a partner application.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
Toast.makeText(this,
String.format("Received result: "%s"", result), Toast.LENGTH_LONG).show();
break;
}
}
}
PkgCertWhitelists.java
package org.jssec.android.shared;
import java.util.HashMap;
import java.util.Map;
import android.content.Context;
public class PkgCertWhitelists {
private Map<String, String> mWhitelists = new HashMap<String, String>();
public boolean add(String pkgname, String sha256) {
if (pkgname == null) return false;
if (sha256 == null) return false;
sha256 = sha256.replaceAll(" ", "");
if (sha256.length() != 64) return false;
// SHA-256 -> 32 bytes -> 64 chars
sha256 = sha256.toUpperCase();
if (sha256.replaceAll("[0-9A-F]+", "").length() != 0) return false; // found non hex char
mWhitelists.put(pkgname, sha256);
return true;
}
public boolean test(Context ctx, String pkgname) {
// Get the correct hash value which corresponds to pkgname.
String correctHash = mWhitelists.get(pkgname);
// Compare the actual hash value of pkgname with the correct hash value.
return PkgCert.test(ctx, pkgname, correctHash);
}
}
PkgCert.java
package org.jssec.android.shared;
63
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import
import
import
import
import
android.content.Context;
android.content.pm.PackageInfo;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
android.content.pm.Signature;
64
There is a risk that a third party application can read an Intent that is used to start the Activity.
Therefore it is necessary to make sure that if you are putting sensitive information inside an Intent
used to start an Activity that you take countermeasures to make sure that it cannot be read by a
malicious third party.
3.
2.
4.
5.
6.
7.
Handle the received intent carefully and securely, even though the intent was sent from an
8.
9.
in-house application.
When exporting an APK from Eclipse, sign the APK with the same developer key as the
destination application.
AndroidManifest.xml
65
android:exported="true"
android:permission="org.jssec.android.activity.inhouseactivity.MY_PERMISSION" />
</application>
</manifest>
InhouseActivity.java
package org.jssec.android.activity.inhouseactivity;
import org.jssec.android.shared.SigPerm;
import org.jssec.android.shared.Utils;
import
import
import
import
import
import
android.app.Activity;
android.content.Context;
android.content.Intent;
android.os.Bundle;
android.view.View;
android.widget.Toast;
66
SigPerm.java
package org.jssec.android.shared;
import
import
import
import
android.content.Context;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
android.content.pm.PermissionInfo;
PkgCert.java
package org.jssec.android.shared;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import
import
import
import
android.content.Context;
android.content.pm.PackageInfo;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
67
import android.content.pm.Signature;
public class PkgCert {
public static boolean test(Context ctx, String pkgname, String correctHash) {
if (correctHash == null) return false;
correctHash = correctHash.replaceAll(" ", "");
return correctHash.equals(hash(ctx, pkgname));
}
public static String hash(Context ctx, String pkgname) {
if (pkgname == null) return null;
try {
PackageManager pm = ctx.getPackageManager();
PackageInfo pkginfo = pm.getPackageInfo(pkgname, PackageManager.GET_SIGNATURES);
if (pkginfo.signatures.length != 1) return null;
// Will not handle multiple signatures.
Signature sig = pkginfo.signatures[0];
byte[] cert = sig.toByteArray();
byte[] sha256 = computeSha256(cert);
return byte2hex(sha256);
} catch (NameNotFoundException e) {
return null;
}
}
private static byte[] computeSha256(byte[] data) {
try {
return MessageDigest.getInstance("SHA-256").digest(data);
} catch (NoSuchAlgorithmException e) {
return null;
}
}
private static String byte2hex(byte[] data) {
if (data == null) return null;
final StringBuilder hexadecimal = new StringBuilder();
for (final byte b : data) {
hexadecimal.append(String.format("%02X", b));
}
return hexadecimal.toString();
}
}
68
*** Point9 *** When exporting an APK from Eclipse, sign the APK with the same developer key as the
destination application.
Figure 4.1-2
69
10. Declare that you want to use the in-house signature permission.
11. Verify that the in-house signature permission is defined by an in-house application.
12. Verify that the destination application is signed with the in-house certificate.
13. Sensitive information can be sent since the destination application is in-house.
15. Handle the received data carefully and securely, even though the data came from an in-house
application.
16. When exporting an APK from Eclipse, sign the APK with the same developer key as the
destination application.
AndroidManifest.xml
InhouseUserActivity.java
package org.jssec.android.activity.inhouseuser;
import org.jssec.android.shared.PkgCert;
import org.jssec.android.shared.SigPerm;
import org.jssec.android.shared.Utils;
import
import
import
import
import
import
70
android.app.Activity;
android.content.ActivityNotFoundException;
android.content.Context;
android.content.Intent;
android.os.Bundle;
android.view.View;
import android.widget.Toast;
public class InhouseUserActivity extends Activity {
// Target Activity information
private static final String TARGET_PACKAGE = "org.jssec.android.activity.inhouseactivity";
private static final String TARGET_ACTIVITY = "org.jssec.android.activity.inhouseactivity.InhouseActivity";
// In-house Signature Permission
private static final String MY_PERMISSION = "org.jssec.android.activity.inhouseactivity.MY_PERMISSION";
// In-house certificate hash value
private static String sMyCertHash = null;
private static String myCertHash(Context context) {
if (sMyCertHash == null) {
if (Utils.isDebuggable(context)) {
// Certificate hash value of "androiddebugkey" in the debug.keystore.
sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255";
} else {
// Certificate hash value of "my company key" in the keystore.
sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";
}
}
return sMyCertHash;
}
private static final int REQUEST_CODE = 1;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
public void onUseActivityClick(View view) {
// *** POINT 11 *** Verify that the in-house signature permission is defined by an in-house application.
if (!SigPerm.test(this, MY_PERMISSION, myCertHash(this))) {
Toast.makeText(this, "The in-house signature permission is not declared by in-house application.",
Toast.LENGTH_LONG).show();
return;
}
// ** POINT 12 *** Verify that the destination application is signed with the in-house certificate.
if (!PkgCert.test(this, TARGET_PACKAGE, myCertHash(this))) {
Toast.makeText(this, "Target application is not an in-house application.", Toast.LENGTH_LONG).show();
return;
}
try {
Intent intent = new Intent();
// *** POINT 13 *** Sensitive information can be sent since the destination application is in-house.
intent.putExtra("PARAM", "Sensitive Info");
// *** POINT 14 *** Use explicit intents to call an In-house Activity.
intent.setClassName(TARGET_PACKAGE, TARGET_ACTIVITY);
startActivityForResult(intent, REQUEST_CODE);
}
catch (ActivityNotFoundException e) {
71
SigPerm.java
package org.jssec.android.shared;
import
import
import
import
android.content.Context;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
android.content.pm.PermissionInfo;
72
PkgCert.java
package org.jssec.android.shared;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import
import
import
import
import
android.content.Context;
android.content.pm.PackageInfo;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
android.content.pm.Signature;
*** Point 18 *** When exporting an APK from Eclipse, sign the APK with the same developer key as the
4.1 Creating/Using Activities
73
destination application.
Figure 4.1-3
74
http://www.jssec.org/dl/android_securecoding_en.pdf
Activities that are Used Only Internally to the Application Must be Set Private
(Required)
2.
4.
Do Not Set the FLAG_ACTIVITY_NEW_TASK Flag for Intents that Start an Activity
3.
5.
6.
(Required)
(Required)
(Required)
(Required)
Use an In-house Defined Signature Permission after Verifying that it is Defined by an In-House
Application
(Required)
(Required)
7.
When Returning a Result, Pay Attention to the Possibility of Information Leakage of that Result
8.
9.
(Required)
Handle the Returned Data from a Requested Activity Carefully and Securely
10. Verify
the
Destination
Activity
if
Linking
with
Another
Company's
(Required)
Application
(Required)
11. When Providing an Asset Secondhand, the Asset should be Protected with the Same Level of
Protection
(Required)
(Recommended)
4.1.2.1. Activities that are Used Only Internally to the Application Must be Set Private
(Required)
Activities which are only used in a single application are not required to be able to receive any Intents
from other applications. Developers often assume that Activities intended to be private will not be
attacked but it is necessary to explicitly make these Activities private in order to stop malicious
Intents from being received.
AndroidManifest.xml
Intent filters should not be set on activities that are only used in a single application. Due to the
characteristics of Intent filters, Due to the characteristics of how Intent filters work, even if you intend
to send an Intent to a Private Activity internally, if you send the Intent through an Intent filter than you
may unintentionally start another Activity. Please see Advanced Topics "4.1.3.1Combining Exported
Attributes and Intent Filter Settings (For Activities)" for more details.
AndroidManifest.xml(Not recommended)
75
android:name=".PictureActivity"
android:label="@string/picture_name"
android:exported="false" >
<intent-filter>
<action android:name="org.jssec.android.activity.OPEN />
</intent-filter>
</activity>
(Required)
In Android OS, Activities are managed by tasks. Task names are determined by the affinity that the
root Activity has. On the other hand, for Activities other than root Activities, the task to which the
Activity belongs is not determined by the Affinity only, but also depends on the Activity's launch
mode. Please refer to "4.1.3.4 Root Activity" for more details.
In the default setting, each Activity uses its package name as its affinity. As a result, tasks are
allocated according to application, so all Activities in a single application will belong to the same task.
To change the task allocation, you can make an explicit declaration for the affinity in the
AndroidManifest.xml file or you can set a flag in an Intent sent to an Activity. However, if you change
task allocations, there is a risk that another application could read the Intents sent to Activities
belonging to another task.
Be sure not to specify android:taskAffinity in the AndroidManifest.xml file and use the default setting
keeping the affinity as the package name in order to prevent sensitive information inside sent or
received Intents from being read by another application.
Below is an example AndroidManifest.xml file for creating and using Private Activities.
AndroidManifest.xml
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<!-- Private activity -->
<!-- *** POINT 1 *** Do not specify taskAffinity -->
<activity
android:name=".PrivateActivity"
android:label="@string/app_name"
android:exported="false" />
</application>
Please refer to the "Google Android Programming guide" 2, the Google Developers's API Guide "Tasks
and Back Stack" 3, "4.1.3.3 Reading Intents Sent to an Activity" and "4.1.3.4 Root Activity" for more
Author Egawa, Fujii, Asano, Fujita, Yamada, Yamaoka, Sano, Takebata, Google Android
http://developer.android.com/guide/components/tasks-and-back-stack.html
76
http://www.jssec.org/dl/android_securecoding_en.pdf
(Required)
The Activity launch mode is used to control the settings for creating new tasks and Activity instances
when starting an Activity. By default it is set to "standard". In the "standard" setting, new instances
are always created when starting an Activity, tasks follow the tasks belonging to the calling Activity,
and it is not possible to create a new task. When a new task is created, it is possible for other
applications to read the contents of the calling Intent so it is required to use the "standard" Activity
launch mode setting when sensitive information is included in an Intent.
The Activity launch mode can be explicitly set in the android:launchMode attribute in the
AndroidManifest.xml file, but because of the reason explained above, this should not be set in the
Activity declaration and the value should be kept as the default "standard".
AndroidManifest.xml
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<!-- Private activity -->
<!-- *** POINT 2 *** Do not specify launchMode -->
<activity
android:name=".PrivateActivity"
android:label="@string/app_name"
android:exported="false" />
</application>
Please refer to "4.1.3.3 Reading Intents Sent to an Activity" and "4.1.3.4 Root Activity."
4.1.2.4. Do Not Set the FLAG_ACTIVITY_NEW_TASK Flag for Intents that Start an Activity(Required)
The
launch
mode
of
an
Activity
can
be
changed
when
executing
startActivity()
or
startActivityForResult() and in some cases a new task may be generated. Therefore it is necessary to
not change the launch mode of Activity during execution.
To change the Activity launch mode, set the Intent flags by using setFlags() or addFlags() and use that
flag used to create a new task. When the FLAG_ACTIVITY_NEW_TASK is set, a new task will be created
if the called Activity does not exist in the background or foreground.
// *** POINT 6 *** Do not set the FLAG_ACTIVITY_NEW_TASK flag for the intent to start an activity.
Intent intent = new Intent(this, PrivateActivity.class);
intent.putExtra("PARAM", "Sensitive Info");
77
startActivityForResult(intent, REQUEST_CODE);
In addition, you may think that there is a way to prevent the contents of an Intent from being read
Please refer to "4.1.3.1Combining Exported Attributes and Intent Filter Settings (For Activities)"
"4.1.3.3 Reading Intents Sent to an Activity" and "4.1.3.4 Root Activity."
(Required)
Risks differ depending on the types of Activity, but when processing a received Intent data, the first
thing you should do is input validation.
Since Public Activities can receive Intents from untrusted sources, they can be attacked by malware.
On the other hand, Private Activities will never receive any Intents from other applications directly,
but it is possible that a Public Activity in the targeted application may forward a malicious Intent to a
Private Activity so you should not assume that Private Activities cannot receive any malicious input.
Since Partner Activities and In-house Activities also have the risk of a malicious intent being
forwarded to them as well, it is necessary to perform input validation on these Intents as well.
(Required)
Make sure to protect your in-house Activities by defining an in-house signature permission when
creating the Activity. Since defining a permission in the AndroidManifest.xml file or declaring a
permission request does not provide adequate security, please be sure to refer to "5.2.1.2 How to
4.1.2.7. When Returning a Result, Pay Attention to the Possibility of Information Leakage of that Result
from the Destination Application
(Required)
When you use setResult() to return data, the reliability of the destination application will depend on
the Activity type. When Public Activities are used to return data, the destination may turn out to be
malware in which case that information could be used in a malicious way. For Private and In-house
Activities, there is not much need to worry about data being returned to be used maliciously because
they are being returned to an application you control. Partner Activities are somewhat in the middle.
As above, when returning data from Activities, you need to pay attention to information leakage from
78
(Required)
When using an Activity by implicit Intents, the Activity in which the Intent gets sent to is determined
by the Android OS. If the Intent is mistakenly sent to malware then Information leakage can occur. On
the other hand, when using an Activity by explicit Intents, only the intended Activity will receive the
Intent so this is much safer.
Unless it is absolutely necessary for the user to determine which application's Activity the intent
should be sent to, you should use explicit intents and specify the destination in advance.
Using an Activity in the same application by an explicit Intent
Intent intent = new Intent(this, PictureActivity.class);
intent.putExtra("BARCODE", barcode);
startActivity(intent);
However, even when using another application's Public Activity by explicit Intents, it is possible that
the destination Activity could be malware. This is because even if you limit the destination by
package name, it is still possible that a malicious application can fake the same package name as the
real application. To eliminate this type of risk, it is necessary to consider using a Partner or In-house.
Please refer to "4.1.3.1Combining Exported Attributes and Intent Filter Settings (For Activities)"
4.1.2.9. Handle the Returned Data from a Requested Activity Carefully and Securely
(Required)
While the risks differ slightly according to what type of Activity you accessing, when processing Intent
data received as a returned value, you always need to perform input validation on the received data.
4.1 Creating/Using Activities
79
http://www.jssec.org/dl/android_securecoding_en.pdf
Public Activities have to accept returned Intents from untrusted sources so when accessing a Public
Activity it is possible that, the returned Intents are actually sent by malware. It is often mistakenly
thought that all returned Intents from a Private Activity are safe because they are originating from the
same application. However, since it is possible that an intent received from an untrusted source is
indirectly forwarded, you should not blindly trust the contents of that Intent. Partner and In-house
Activities have a risk somewhat in the middle of Private and Public Activities. Be sure to input validate
these Activities as well.
Please refer to "3.2 Handling Input Data Carefully and Securely" for more information.
4.1.2.10. Verify
the
Destination
Activity
if
Linking
with
Another
Company's
Application
(Required)
Be sure to sure a whitelist when linking with another company's application. You can do this by
saving a copy of the company's certificate hash inside your application and checking it with the
certificate hash of the destination application. This will prevent a malicious application from being
able to spoof Intents. Please refer to sample code section "4.1.1.3 Creating/Using Partner Activities"
for the concrete implementation method. For technical details, please refer to "4.1.3.2 Validating the
Requesting Application."
80
4.1.2.11. When Providing an Asset Secondhand, the Asset should be Protected with the Same Level of
Protection
(Required)
application secondhand, you need to make sure that it has the same required permissions needed to
access the asset. In the Android OS permission security model, only an application that has been
granted proper permissions can directly access a protected asset. However, there is a loophole
because an application with permissions to an asset can act as a proxy and allow access to an
(Recommended)
You should not send sensitive information to untrusted parties. Even when you are linking with a
specific application, there is still a chance that you unintentionally send an Intent to a different
application or that a malicious third party can steal your Intents. Please refer to "4.1.3.5 Log Output
When using Activities."
You need to consider the risk of information leakage when sending sensitive information to an
Activity. You must assume that all data in Intents sent to a Public Activity can be obtained by a
malicious third party. In addition, there is a variety of risks of information leakage when sending
Intents to Partner or In-house Activities as well depending on the implementation. Even when
sending data to Private Activities, there is a risk that the data in the Intent could be leaked through
LogCat. Information in the extras part of the Intent is not output to LogCat so it is best to store
sensitive information there.
However, not sending sensitive data in the first place is the only perfect solution to prevent
information leakage therefore you should limit the amount of sensitive information being sent as
much as possible. When it is necessary to send sensitive information, the best practice is to only send
to a trusted Activity and to make sure the information cannot be leaked through LogCat.
In addition, sensitive information should never be sent to the root Activity. Root Activities are
Activities that are called first when a task is created. For example, the Activity which is launched from
launcher is always the root Activity.
Please refer to "4.1.3.3 Reading Intents Sent to an Activity" and "4.1.3.4 Root Activity" for more
81
http://www.jssec.org/dl/android_securecoding_en.pdf
Public Activities, Partner Activities, and In-house Activities. The various combinations of permitted
settings for each type of exported attribute defined in the AndroidManifest.xml file and the
intent-filter elements are defined in the table below. Please verify the compatibility of the exported
attribute and intent-filter element with the Activity you are trying to create.
Table 4.1-2
Value of exported attribute
true
False
Not specified
Public, Partner
Public, Partner
Intent
Public, Partner,
AndroidManifest.xml
Private
Defined
Filter
Not
In- house
The reason why an undefined intent filter and an exported attribute of false should not be used is
that there is a loophole in Android's behavior, and because of how Intent filters work, other
application's Activities can be called unexpectedly. The following two figures below show this
explanation. Figure 4.1-4 is an example of normal behavior in which a Private Activity (Application A)
can be called by an implicit Intent only from the same application. The Intent filter (action = "X") is
defined to work only inside Application A, so this is the expected behavior.
Application A
Call an activity with
the implicit intent
Intent(X)
Application C
Call the activity with
the implicit intent
Intent(X)
Figure 4.1-4
Figure 4.1-5 below shows a scenario in which the same Intent filter (action="X") is defined in
Application B as well as Application A. Application A is trying to call a Private Activity in the same
application by sending an implicit Intent, but this time a dialogue box asking the user which
application to select is displayed, and the Public Activity B-1 in Application B called by mistake due to
82
http://www.jssec.org/dl/android_securecoding_en.pdf
the user selection. Due to this loophole, it is possible that sensitive information can be sent to other
applications or application may receive an unexpected retuned value.
Application A
Call an activity with
the implicit intent
Application
selector
Intent(X)
A-1
B-1
Application B
Public Activity B-1
exported=true
action=X
Android device
Figure 4.1-5
As shown above, using Intent filters to send implicit Intents to Private Activities may result in
unexpected behavior so it is best to avoid this setting. In addition, we have verified that this behavior
does not depend on the installation order of Application A and Application B.
4.1.3.2. Validating the Requesting Application
Here we explain the technical information about how to implement a Partner Activity. Partner
applications permit that only particular applications which are registered in a whitelist are allowed
access and all other applications are denied. Because applications other than in-house applications
also need access permission, we cannot use signature permissions for access control.
Simply speaking, we want to validate the application trying to use the Partner Activity by checking if it
is registered in a predefined whitelist and allow access if it is and deny access if it is not. Application
validation is done by obtaining the certificate from the application requesting access and comparing
its hash with the one in the whitelist.
Some developers may think that it is sufficient to just compare the package name without obtaining
the certificate, however, it is easy to spoof the package name of a legitimate application so this is not
a good method to check for authenticity. Arbitrarily assignable values should not be used for
authentication. On the other hand, because only the application developer has the developer key for
signing its certificate, this is a better method for identification. Since the certificate cannot be easily
4.1 Creating/Using Activities
83
spoofed, unless a malicious third party can steal the developer key, there is a very small chance that
malicious application will be trusted. While it is possible to store the entire certificate in the whitelist,
it is sufficient to only store the SHA-256 hash value in order to minimize the file size.
There are two restrictions for using this method.
The second restriction is the restriction imposed as a result of the first restriction, so technically
there is only a single restriction.
This restriction occurs due to the restriction of Activity.getCallingPackage() which gets the package
name of the calling application. Activity.getCallingPackage() returns the package name of source
(requesting) application only in case it is called by startActivityForResult(), but unfortunately, when it
is called by startActivity(), it only returns null. Because of this, when using the method explained here,
the source (requesting) application needs to use startActivityForResult() even if it does not need to
obtain a return value. In addition, startActivityForResult() can be used only in Activity classes, so the
source (requester) is limited to Activities.
PartnerActivity.java
package org.jssec.android.activity.partneractivity;
import org.jssec.android.shared.PkgCertWhitelists;
import org.jssec.android.shared.Utils;
import
import
import
import
import
import
android.app.Activity;
android.content.Context;
android.content.Intent;
android.os.Bundle;
android.view.View;
android.widget.Toast;
84
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// *** POINT 4 *** Verify the requesting application's certificate through a predefined whitelist.
if (!checkPartner(this, getCallingPackage())) {
Toast.makeText(this,
"Requesting application is not a partner application.",
Toast.LENGTH_LONG).show();
finish();
return;
}
// *** POINT 5 *** Handle the received intent carefully and securely, even though the intent was sent from a p
artner application.
// Omitted, since this is a sample. Refer to "3.2 Handling Input Data Carefully and Securely."
Toast.makeText(this, "Accessed by Partner App", Toast.LENGTH_LONG).show();
}
public void onReturnResultClick(View view) {
// *** POINT 6 *** Only return Information that is granted to be disclosed to a partner application.
Intent intent = new Intent();
intent.putExtra("RESULT", "Information for partner applications");
setResult(RESULT_OK, intent);
finish();
}
}
PkgCertWhitelists.java
package org.jssec.android.shared;
import java.util.HashMap;
import java.util.Map;
import android.content.Context;
public class PkgCertWhitelists {
private Map<String, String> mWhitelists = new HashMap<String, String>();
public boolean add(String pkgname, String sha256) {
if (pkgname == null) return false;
if (sha256 == null) return false;
sha256 = sha256.replaceAll(" ", "");
if (sha256.length() != 64) return false;
// SHA-256 -> 32 bytes -> 64 chars
sha256 = sha256.toUpperCase();
if (sha256.replaceAll("[0-9A-F]+", "").length() != 0) return false; // found non hex char
mWhitelists.put(pkgname, sha256);
return true;
}
public boolean test(Context ctx, String pkgname) {
// Get the correct hash value which corresponds to pkgname.
String correctHash = mWhitelists.get(pkgname);
85
// Compare the actual hash value of pkgname with the correct hash value.
return PkgCert.test(ctx, pkgname, correctHash);
}
}
PkgCert.java
package org.jssec.android.shared;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import
import
import
import
import
android.content.Context;
android.content.pm.PackageInfo;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
android.content.pm.Signature;
86
Activity started in a task. It is possible for any application to read the Intents added to the task history
by using the ActivityManager class.
Sample code for reading the task history from an application is shown below. To browse the task
history, specify the GET_TASKS permission in the AndroidManifest.xml file.
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.jssec.android.intent.maliciousactivity"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="15" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MaliciousActivity"
android:label="@string/title_activity_main" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<!-- Use GET_TASKS Permission -->
<uses-permission android:name="android.permission.GET_TASKS" />
</manifest>
MaliciousActivity.java
package org.jssec.android.intent.maliciousactivity;
import java.util.List;
import
import
import
import
import
android.app.Activity;
android.app.ActivityManager;
android.content.Intent;
android.os.Bundle;
android.util.Log;
87
http://www.jssec.org/dl/android_securecoding_en.pdf
super.onCreate(savedInstanceState);
setContentView(R.layout.malicious_activity);
// Get am ActivityManager instance.
ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
// Get 100 recent task info.
List<ActivityManager.RecentTaskInfo> list = activityManager
.getRecentTasks(100, ActivityManager.RECENT_WITH_EXCLUDED);
for (ActivityManager.RecentTaskInfo r : list) {
// Get Intent sent to root Activity and Log it.
Intent intent = r.baseIntent;
Log.v("baseIntent", intent.toString());
}
}
}
You can obtain specified entries of the task history by using the getRecentTasks() function of the
AcitivityManager
class.
Information
about
each
task
is
stored
in
an
instance
of
the
ActivityManager.RecentTaskInfo class, but Intents that were sent to the task's root Activity are stored
in its member variable baseIntent. Since the root Activity is the Activity which was started when the
task was created, please be sure to fulfill the following two conditions when calling an Activity.
The called Activity is the task's root Activity which already exists in the background or
foreground.
launcher, this Activity will be the root Activity. According to the Android specifications, the contents
of Intents sent to the root Activity can be read from arbitrary applications. So, it is necessary to take
countermeasures not to send sensitive information to the root Activity. In this guidebook, the
following three rules have been made to avoid a called Activity to become root Activity.
We consider the situations that an Activity can become the root Activity below. A called Activity
becoming a root Activity depends on the following.
First of all, let me explain the "Launch mode of called Activity." Launch mode of Activity can be set by
writing android:launchMode in AndroidManifest.xml. When it's not written, it's considered as
88
"standard". In addition, launch mode can be also changed by a flag to set to Intent. Flag
"FLAG_ACTIVITY_NEW_TASK" launches Activity by "singleTask" mode.
standard
Activity which is called by this mode won't be root, and it belongs to the caller side task. Every
This launch mode is the same as "standard", except for that the instance is not generated when
launching an Activity which is displayed in most front side of foreground task.
singleTask
This launch mode determines the task to which the activity would be belonging by Affinity value.
When task which is matched with Activity's affinity doesn't exist either in background or in
foreground, a new task is generated along with Activity's instance. When task exists, neither of
them is to be generated. In the former one, the launched Activity's Instance becomes root.
singleInstance
Same as "singleTask", but following point is different. Only root Activity can belongs to the newly
generated task. So instance of Activity which was launched by this mode is always root activity.
Now, we need to pay attention to the case that the class name of called Activity and the class
name of Activity which is included in a task are different although the task which has the same
name of called Activity's affinity already exists.
From as above, we can get to know that Activity which was launched by "singleTask" or
"singleInstance" has the possibility to become root. In order to secure the application's safety, it
Next, I'll explain about "Task of the called Activity and its launch mode". Even if Activity is called by
"standard" mode, it becomes root Activity in some cases depends on the task state to which Activity
belongs.
For example, think about the case that called Activity's task has being run already in background.
The problem here is the case that Activity Instance of the task is launched by singleInstance". When
the affinity of Activity which was called by "standard" is same with the task, new task is to be
generated by the restriction of existing "singleInstance" Activity. However, when class name of each
Activity is same, task is not generated and existing activity Instance is to be used. In any cases, that
called Activity becomes root Activity.
As per above, the conditions that root Activity is called are complicated, for example it depends on
the state of execution. So when developing applications, it's better to contrive that Activity is called
89
by "standard".
As an example of that Intent which is sent to Private Activity is read out form other application, the
sample code shows the case that caller side Activity of private Activity is launched by "singleInstance"
mode. In this sample code, private activity is launched by "standard" mode, but this private Activity
becomes root Activity of new task due the "singleInstance" condition of caller side Activity. At this
moment, sensitive information that is sent to Private Activity is recorded task history, so it can be
read out from other applications. FYI, both caller side Activity and Private Activity have the same
affinity.
AndroidManifest.xml(Not recommended)
package org.jssec.android.activity.singleinstanceactivity;
import
import
import
import
90
android.app.Activity;
android.content.Intent;
android.os.Bundle;
android.view.View;
import android.widget.Toast;
public class PrivateActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.private_activity);
// Handle intent securely, even though the intent sent from the same application.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
String param = getIntent().getStringExtra("PARAM");
Toast.makeText(this, String.format("Received param: "%s"", param), Toast.LENGTH_LONG).show();
}
public void onReturnResultClick(View view) {
Intent intent = new Intent();
intent.putExtra("RESULT", "Sensitive Info");
setResult(RESULT_OK, intent);
finish();
}
}
In caller side of Private Activity, Private Activity is launched by "standard" mode without setting flag to
Intent.
PrivateUserActivity.java
package org.jssec.android.activity.singleinstanceactivity;
import
import
import
import
import
android.app.Activity;
android.content.Intent;
android.os.Bundle;
android.view.View;
android.widget.Toast;
91
contents are to be output to LogCat, so in this case, sensitive information should not be included
here.
For example, when an application sent mails, the mail address is unfortunately outputted to LogCat if
the application would specify the mail address to URI. So, better to send by setting Extras.
When sending a mail as below, mail address is shown to the logCat.
MainActivity.java
92
http://www.jssec.org/dl/android_securecoding_en.pdf
You can find your Broadcast Receiver in the following judgment flow. The receiving applications
cannot check the package names of Broadcast-sending applications that are necessary for linking
with the partners. As a result, Broadcast Receiver for the partners cannot be created.
Table 4.2-1 Definition of broadcast receiver types
Type
Definition
Private broadcast
A broadcast receiver that can receive broadcasts only from the same
Public
receiver
receiver
broadcast
In-house
broadcast receiver
Yes
No
Yes
No
Figure 4.2-1
In addition, Broadcast Receiver can be divided into 2 types based on the definition methods, Static
Broadcast Receiver and Dynamic Broadcast Receiver. The differences between them can be found in
the following figure. In the sample code, an implementation method for each type is shown. The
implementation method for sending applications is also described because the countermeasure for
sending information is determined depending on the receivers.
93
Table 4.2-2
Static Broadcast
Receiver
Definition method
Define by writing
Characteristic
<receiver> elements in
AndroidManifest.xml
Dynamic Broadcast
Receiver
unregisterReceiver() in a
registerReceiver() and
program,
register/unregister
Broadcast Receiver
94
By calling
dynamically.
created.
the application can be received. Dynamic Broadcast Receiver cannot be registered as Private, so
Private Broadcast Receiver consists of only Static Broadcast Receivers.
2.
Handle the received intent carefully and securely, even though the intent was sent from within
3.
Sensitive information can be sent as the returned results since the requests come from within the
same application.
AndroidManifest.xml
PrivateReceiver.java
package org.jssec.android.broadcast.privatereceiver;
import
import
import
import
import
android.app.Activity;
android.content.BroadcastReceiver;
android.content.Context;
android.content.Intent;
android.widget.Toast;
95
@Override
public void onReceive(Context context, Intent intent) {
// *** POINT 2 *** Handle the received intent carefully and securely,
// even though the intent was sent from within the same application.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
String param = intent.getStringExtra("PARAM");
Toast.makeText(context,
String.format("Received param: "%s"", param),
Toast.LENGTH_SHORT).show();
// *** POINT 3 *** Sensitive information can be sent as the returned results since the requests come from with
in the same application.
setResultCode(Activity.RESULT_OK);
setResultData("Sensitive Info from Receiver");
abortBroadcast();
}
}
96
The sample code for sending Broadcasts to private Broadcast Receiver is shown below. Please pay
attention that Sticky cannot be used here though the method of sending Broadcasts to private
Broadcast Receiver is said to be safe from the security point of view.
Points (Sending Broadcasts):
4.
Use the explicit Intent with class specified to call a receiver within the same application.
5.
Sensitive information can be sent since the destination Receiver is within the same application.
6.
Handle the received result data carefully and securely, even though the data came from the
PrivateSenderActivity.java
package org.jssec.android.broadcast.privatereceiver;
import
import
import
import
import
import
import
android.app.Activity;
android.content.BroadcastReceiver;
android.content.Context;
android.content.Intent;
android.os.Bundle;
android.view.View;
android.widget.TextView;
97
};
private TextView mLogView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mLogView = (TextView)findViewById(R.id.logview);
}
private void logLine(String line) {
mLogView.append(line);
mLogView.append("n");
}
}
98
malware.
2.
Public Receiver which is the sample code for public Broadcast Receiver can be used both in static
Broadcast Receiver and Dynamic Broadcast Receiver.
PublicReceiver.java
package org.jssec.android.broadcast.publicreceiver;
import
import
import
import
import
android.app.Activity;
android.content.BroadcastReceiver;
android.content.Context;
android.content.Intent;
android.widget.Toast;
*** POINT 1 *** Handle the received Intent carefully and securely.
Since this is a public broadcast receiver, the requesting application may be malware.
Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
(MY_BROADCAST_PUBLIC.equals(intent.getAction())) {
String param = intent.getStringExtra("PARAM");
Toast.makeText(context,
String.format("%s:nReceived param: "%s"", getName(), param),
Toast.LENGTH_SHORT).show();
}
// *** POINT 2 *** When returning a result, do not include sensitive information.
// Since this is a public broadcast receiver, the requesting application may be malware.
// If no problem when the information is taken by malware, it can be returned as result.
setResultCode(Activity.RESULT_OK);
setResultData(String.format("Not Sensitive Info from %s", getName()));
abortBroadcast();
}
}
99
http://www.jssec.org/dl/android_securecoding_en.pdf
Receiver Instance is longer than PublicReceiverActivity, it cannot be kept as the member variable of
PublicReceiverActivity. In this case, keep the Dynamic Broadcast Receiver Instance as the member
variable
of
DynamicReceiverService,
and
then
start/end
DynamicReceiverService
package org.jssec.android.broadcast.publicreceiver;
import
import
import
import
import
100
android.app.Service;
android.content.Intent;
android.content.IntentFilter;
android.os.IBinder;
android.widget.Toast;
from
PublicReceiverActivity.java
package org.jssec.android.broadcast.publicreceiver;
import
import
import
import
android.app.Activity;
android.content.Intent;
android.os.Bundle;
android.view.View;
101
102
Next, the sample code for sending Broadcasts to public Broadcast Receiver is shown. When sending
Broadcasts to public Broadcast Receiver, it's necessary to pay attention that Broadcasts can be
received by malware.
4.
When receiving a result, handle the result data carefully and securely.
PublicSenderActivity.java
package org.jssec.android.broadcast.publicsender;
import
import
import
import
import
import
import
android.app.Activity;
android.content.BroadcastReceiver;
android.content.Context;
android.content.Intent;
android.os.Bundle;
android.view.View;
android.widget.TextView;
103
104
2.
4.
3.
5.
Require the in-house signature permission by the Static Broadcast Receiver definition.
6.
Handle the received intent carefully and securely, even though the Broadcast was sent from an
7.
8.
in-house application.
When Exporting an APK from Eclipse, sign the APK with the same developer key as the sending
application.
In-house Receiver which is a sample code of in-house Broadcast Receiver is to be used both in Static
package org.jssec.android.broadcast.inhousereceiver;
import org.jssec.android.shared.SigPerm;
import org.jssec.android.shared.Utils;
import
import
import
import
import
android.app.Activity;
android.content.BroadcastReceiver;
android.content.Context;
android.content.Intent;
android.widget.Toast;
105
*** POINT 6 *** Handle the received intent carefully and securely,
even though the Broadcast was sent from an in-house application..
Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
(MY_BROADCAST_INHOUSE.equals(intent.getAction())) {
String param = intent.getStringExtra("PARAM");
Toast.makeText(context,
String.format("%s:nReceived param: "%s"", getName(), param),
Toast.LENGTH_SHORT).show();
}
// *** POINT 7 *** Sensitive information can be returned since the requesting application is in-house.
setResultCode(Activity.RESULT_OK);
setResultData(String.format("Sensitive Info from %s", getName()));
abortBroadcast();
}
}
106
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<!-- *** POINT 3 *** Require the in-house signature permission by the Static Broadcast Receiver definition. ->
<receiver
android:name=".InhouseReceiver"
android:permission="org.jssec.android.broadcast.inhousereceiver.MY_PERMISSION">
<intent-filter>
<action android:name="org.jssec.android.broadcast.MY_BROADCAST_INHOUSE" />
</intent-filter>
</receiver>
<service
android:name=".DynamicReceiverService"
android:exported="false" />
<activity
android:name=".InhouseReceiverActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
operations, the button is arranged on InhouseReceiverActivity. Since the scope of Dynamic Broadcast
Receiver Instance is longer than InhouseReceiverActivity, it cannot be kept as the member variable of
InhouseReceiverActivity. So, keep Dynamic Broadcast Receiver Instance as the member variable of
DynamicReceiverService, and then start/end DynamicReceiverService from InhouseReceiverActivity
to register/unregister Dynamic Broadcast Receiver indirectly.
InhouseReceiverActivity.java
package org.jssec.android.broadcast.inhousereceiver;
import
import
import
import
android.app.Activity;
android.content.Intent;
android.os.Bundle;
android.view.View;
107
}
public void onUnregisterReceiverClick(View view) {
Intent intent = new Intent(this, DynamicReceiverService.class);
stopService(intent);
}}
DynamicReceiverService.java
package org.jssec.android.broadcast.inhousereceiver;
import
import
import
import
import
android.app.Service;
android.content.Intent;
android.content.IntentFilter;
android.os.IBinder;
android.widget.Toast;
108
SigPerm.java
package org.jssec.android.shared;
import
import
import
import
android.content.Context;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
android.content.pm.PermissionInfo;
PkgCert.java
package org.jssec.android.shared;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import
import
import
import
import
android.content.Context;
android.content.pm.PackageInfo;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
android.content.pm.Signature;
109
110
*** Point 8 *** When exporting an APK from Eclipse, sign the APK with the same developer key as the
sending application.
Figure 4.2-2
111
Next, the sample code for sending Broadcasts to in-house Broadcast Receiver is shown. When
Signature Permission of Broadcast Receiver side. So it's necessary to pay attention that there is a
11. Verify that the in-house signature permission is defined by an in-house application.
12. Sensitive information can be returned since the requesting application is the in-house one.
13. Require the in-house signature permission of Receivers.
15. When exporting an APK from Eclipse, sign the APK with the same developer key as the
destination application.
AndroidManifest.xml
InhouseSenderActivity.java
package org.jssec.android.broadcast.inhousesender;
import org.jssec.android.shared.SigPerm;
import org.jssec.android.shared.Utils;
112
import
import
import
import
import
import
import
import
android.app.Activity;
android.content.BroadcastReceiver;
android.content.Context;
android.content.Intent;
android.os.Bundle;
android.view.View;
android.widget.TextView;
android.widget.Toast;
113
SigPerm.java
package org.jssec.android.shared;
import
import
import
import
android.content.Context;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
android.content.pm.PermissionInfo;
114
PkgCert.java
package org.jssec.android.shared;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import
import
import
import
import
android.content.Context;
android.content.pm.PackageInfo;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
android.content.pm.Signature;
115
*** Point 15 *** When exporting an APK from Eclipse, sign the APK with the same developer key as the
destination application.
Figure 4.2-3
116
2.
3.
(Required)
(Required)
Application
(Required)
Destination Application
(Required)
(Required)
Delivered
(Required)
Use the In-house Defined Signature Permission after Verifying that it's Defined by an In-house
4.
When Returning a Result Information, Pay Attention to the Result Information Leakage from the
5.
When Sending Sensitive Information with a Broadcast, Limit the Receivable Receiver (Required)
7.
Pay Attention that the Ordered Broadcast without Specifying the receiverPermission May Not Be
6.
8.
9.
Handle the Returned Result Data from the Broadcast Receiver Carefully and Securely (Required)
When Providing an Asset Secondarily, the Asset should be protected with the Same Protection
Level
4.2.2.1. Broadcast Receiver that Is Used Only in an Application Must Be Set as Private
(Required)
(Required)
Broadcast Receiver which is used only in the application should be set as private to avoid from
receiving any Broadcasts from other applications unexpectedly. It will prevent the application
function abuse or the abnormal behaviors.
Receiver used only within the same application should not be designed with setting Intent-filter.
Because of the Intent-filter characteristics, a public Receiver of other application may be called
unexpectedly by calling through Intent-filter even though a private Receiver within the same
application is to be called.
AndroidManifest.xml(Not recommended)
Please refer to "4.2.3.1 Combinations of the exported Attribute and the Intent-filter setting (For
Receiver)."
(Required)
Though risks are different depending on the types of the Broadcast Receiver, firstly verify the safety
4.2 Receiving/Sending Broadcasts
117
may receive malware's attacking Intents. Private Broadcast Receiver will never receive any Intent from
other applications directly, but Intent data which a public Component received from other
applications may be forwarded to Private Broadcast Receiver. So don't think that the received Intent is
totally safe without any qualification. In-house Broadcast Receivers have some degree of the risks, so
(Required)
In-house Broadcast Receiver which receives only Broadcasts sent by an In-house application should
be protected by in-house-defined Signature Permission. Permission definition/Permission request
declarations in AndroidManifest.xml are not enough to protecting, so please refer to "5.2.1.2 How to
Communicate Between In-house Applications with In-house-defined Signature Permission." ending
4.2.2.4. When Returning a Result Information, Pay Attention to the Result Information Leakage from
the Destination Application
(Required)
The Reliability of the application which returns result information by setResult() varies depending on
the types of the Broadcast Receiver. In case of Public Broadcast Receiver, the destination application
may be malware, and there may be a risk that the result information is used maliciously. In case of
Private Broadcast Receiver and In-house Broadcast Receiver, the result destination is In-house
Need to pay attention to the result information leakage from the destination application when result
information is returned from Broadcast Receivers as above.
4.2.2.5. When Sending Sensitive Information with a Broadcast, Limit the Receivable Receiver
(Required)
Broadcast is the created system to broadcast information to unspecified large number of applications
or notify them of the timing at once. So, broadcasting sensitive information requires the careful
designing for preventing the illicit obtainment of the information by malware.
For broadcasting sensitive information, only reliable Broadcast Receiver can receive it, and other
Broadcast Receivers cannot. The following are some examples of Broadcast sending methods.
118
The method is to fix the address by Broadcast-sending with an explicit Intent for sending
All rights reserved Japan Smartphone Security Association.
Broadcasts to the intended reliable Broadcast Receivers only. There are 2 patterns in this method.
When it's addressed to a Broadcast Receiver within the same application, specify the address
When it's addressed to a Broadcast Receiver in other applications, specify the address by
Intent#setClassName(String, String). Confirm the permitted application by comparing the
developer key of the APK signature in the destination package with the white list to send
Broadcasts. Actually the following method of using implicit Intents is more practical.
receiverPermission parameter and make the reliable Broadcast Receiver declare to use this
Signature Permission. Refer to the sample code section "4.2.1.3 In-house Broadcast Receiver Receiving/Sending Broadcast" for the concrete code. In addition, implementing this
Broadcast-sending method needs to apply the rule "4.2.2.3 Use the In-house Defined Signature
Permission after Verifying that it's Defined by an In-house Application
(Required)."
(Required)
Usually, the Broadcasts will be disappeared when they are processed to be received by the available
Broadcast Receivers. On the other hand, Sticky Broadcasts (hereafter, Sticky Broadcasts including
Sticky Ordered Broadcasts), will not be disappeared from the system even when they processed to be
received by the available Broadcast Receivers and will be able to be received by registerReceiver().
When Sticky Broadcast becomes unnecessary, it can be deleted anytime arbitrarily with
removeStickyBroadcast().
As it's presupposed that Sticky Broadcast is used by the implicit Intent. Broadcasts with specified
receiverPermission Parameter cannot be sent. So information sent by Sticky Broadcast may be taken
by unspecified large number of applications including malware. As a result, sensitive information
4.2.2.7. Pay Attention that the Ordered Broadcast without Specifying the receiverPermission May Not
Be Delivered
(Required)
large number of applications including malware. Ordered Broadcast is used to receive the returned
information from Receiver, and to make several Receivers execute processing one by one. Broadcasts
are sent to the Receivers in order of priority. So if the high- priority malware receives Broadcast first
4.2.2.8. Handle the Returned Result Data from the Broadcast Receiver Carefully and Securely
(Required)
Basically the result data should be processed safely considering the possibility that received results
may be the attacking data though the risks vary depending on the types of the Broadcast Receiver
4.2 Receiving/Sending Broadcasts
119
When sender (source) Broadcast Receiver is public Broadcast Receiver, it receives the returned data
from unspecified large number of applications. So it may also receive malware's attacking data. When
sender (source) Broadcast Receiver is private Broadcast Receiver, it seems no risk. However the data
received by other applications may be forwarded as result data indirectly. So the result data should
not be considered as safe without any qualification. When sender (source) Broadcast Receiver is
In-house Broadcast Receiver, it has some degree of the risks. So it should be processed in a safe way
considering the possibility that the result data may be an attacking data.
Please refer to "3.2 Handling Input Data Carefully and Securely"
4.2.2.9. When Providing an Asset Secondarily, the Asset should be protected with the Same Protection
Level
(Required)
When information or function assets protected by Permission are provided to other applications
secondarily, it's necessary to keep the protection standard by claiming the same Permission of the
destination application. In the Android Permission security models, privileges are managed only for
the direct access to the protected assets from applications. Because of the characteristics, acquired
assets may be provided to other applications without claiming Permission which is necessary for
120
http://www.jssec.org/dl/android_securecoding_en.pdf
implementing Receivers. The reason why the usage of exported="false" with Intent-filter definition is
false
Not
specified
Intent-filter defined
OK
OK
Intent
OK
OK
Prohibited
Filter
Not
Defined
Public Receivers in other applications may be called unexpectedly even though Broadcasts are sent to
the private Receivers within the same applications. This is the reason why specifying
exported="false" with Intent-filter definition is prohibited. The following 2 figures show how the
unexpected calls occur.
Figure 4.2-4 is an example of the normal behaviors which a private Receiver (application A) can be
called by implicit Intent only within the same application. Intent-filter (in the figure, action="X") is
Application A
Send a broadcast with
the implicit intent
Intent(X)
Application C
Send a broadcast with
the implicit intent
Intent(X)
Figure 4.2-4
Figure 4.2-5 is an example that Intent-filter (see action="X" in the figure) is defined in the
application B as well as in the application A. First of all, when another application (application C)
sends Broadcasts by implicit Intent, they are not received by a private Receiver (A-1) side. So there
121
http://www.jssec.org/dl/android_securecoding_en.pdf
won't be any security problem. (See the orange arrow marks in the Figure.)
From security point of view, the problem is application A's call to the private Receiver within the same
application. When the application A broadcasts implicit Intent, not only private Receiver within the
same application, but also public Receiver (B-1) with the same Intent-filter definition can also receive
the Intent. (Red arrow marks in the Figure). In this case, sensitive information may be sent from the
application A to B. When the application B is malware, it will cause the leakage of sensitive
information. When the Broadcast is Ordered Broadcast, it may receive the unexpected result
information.
Application A
Send a broadcast with
the implicit intent
Intent(X)
Application C
Send a broadcast with
the implicit intent
Intent(X)
Application B
Public Receiver B-1
exported=true
action=X
Android device
Figure 4.2-5
However, exported="false" with Intent-filter definition should be used when Broadcast Receiver to
receive only Broadcast Intent sent by the system is implemented. Other combination should not be
used. This is based on the fact that Broadcast Intent sent by the system can be received by
exported="false". If other applications send Intent which has same ACTION with Broadcast Intent
sent by system, it may cause an unexpected behavior by receiving it. However, this can be prevented
by specifying exported="false".
4.2.3.2. Receiver Won't Be Registered before Launching the Application in Android 3.1 or later
In Android 3.1 or later, it's necessary to pay attention that Broadcast Receiver which is statically
http://www.jssec.org/dl/android_securecoding_en.pdf
launched.
4.2.3.3. Private Broadcast Receiver Can Receive the Broadcast that Was Sent by the Same UID
Application
Same UID can be provided to several applications. Even if it's private Broadcast Receiver, the
However, it won't be a security problem. Since it's guaranteed that applications with the same UID
have the consistent developer keys for signing APK. It means that what private Broadcast Receiver
receives is only the Broadcast sent from In-house applications.
4.2.3.4. Types and Features of Broadcasts
Regarding Broadcasts, there are 4 types based on the combination of whether it's Ordered or not, and
Sticky or not. Based on Broadcast sending methods, a type of Broadcast to send is determined.
Table 4.2-4
Type of Broadcast
Ordered?
Sticky?
sendOrderedBroadcast()
Yes
No
sendStickyOrderedBroadcast()
Yes
Normal Broadcast
sendBroadcast()
Sticky Broadcast
sendStickyBroadcast()
Ordered Broadcast
Sticky Ordered Broadcast
No
No
No
Yes
Yes
Normal Broadcast
Receivers.
in
order
with
receivable
Broadcast
Receivers.
The
Broadcast.
123
http://www.jssec.org/dl/android_securecoding_en.pdf
Sticky Broadcast
Sticky Broadcast does not disappear and remains in the system, and
then the application that calls registerReceiver() can receive Sticky
Broadcast later. Since Sticky Broadcast is different from other
of Sticky Broadcast.
From the Broadcast characteristic behavior point of view, above table is conversely arranged in the
following one.
Table 4.2-6
Characteristic
Broadcast
Limit
behavior
Broadcast
of
Receivers
Broadcast
Receivers
Normal
Ordered
Sticky
Sticky Ordered
Broadcast
Broadcast
Broadcast
Broadcast
OK
OK
OK
OK
OK
OK
OK
OK
when lacking Permission causes errors in receiver/sender side. Intent information sent by Broadcast
is included in the error log, so after an error occurs it's necessary to pay attention that Intent
124
125
http://www.jssec.org/dl/android_securecoding_en.pdf
4.3.
Since the interface of ContentResolver and SQLiteDatabase are so much alike, it's often
misunderstood that Content Provider is so closely related to SQLiteDatabase. However, actually
Content Provider simply provides the interface of inter-application data sharing, so it's necessary to
pay attention that it does not interfere each data saving format. To save data in Content Provider,
SQLiteDatabase can be used, and other saving formats, such as an XML file format, also can be used.
Any data saving process is not included in the following sample code, so please add it if needed.
4.3.1. Sample Code
The risks and countermeasures of using Content Provider differ depending on how that Content
Provider is being used. In this section, we have classified 5 types of Content Provider based on how
the Content Provider is being used.
You can find out which type of Content Provider you are
Definition
Private Content
Public Content
Partner Content
In-house Content
Temporary permit
Provider
Provider
Provider
Provider
Content Provider
applications
Yes
No
Provide services always?
Yes
No
Use only in
the same application?
Yes
No
Yes
Figure 4.3-1
126
No
Temporary
Content Provider
2.
3.
4.
Do not (Cannot) implement Private Content Provider in Android 2.2 (API Level 8) or earlier.
Handle the received request data carefully and securely, even though the data comes from the
same application.
Sensitive information can be sent since it is sending and receiving all within the same application.
AndroidManifest.xml
PrivateProvider.java
package org.jssec.android.provider.privateprovider;
import
import
import
import
android.content.ContentProvider;
android.content.ContentUris;
android.content.ContentValues;
android.content.UriMatcher;
127
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
public class PrivateProvider extends ContentProvider {
public static final String AUTHORITY = "org.jssec.android.provider.privateprovider";
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.org.jssec.contenttype";
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.org.jssec.contenttype";
// Expose the interface that the Content Provider provides.
public interface Download {
public static final String PATH = "downloads";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH);
}
public interface Address {
public static final String PATH = "addresses";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH);
}
// UriMatcher
private static final int DOWNLOADS_CODE = 1;
private static final int DOWNLOADS_ID_CODE = 2;
private static final int ADDRESSES_CODE = 3;
private static final int ADDRESSES_ID_CODE = 4;
private static UriMatcher sUriMatcher;
static {
sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
sUriMatcher.addURI(AUTHORITY, Download.PATH, DOWNLOADS_CODE);
sUriMatcher.addURI(AUTHORITY, Download.PATH + "/#", DOWNLOADS_ID_CODE);
sUriMatcher.addURI(AUTHORITY, Address.PATH, ADDRESSES_CODE);
sUriMatcher.addURI(AUTHORITY, Address.PATH + "/#", ADDRESSES_ID_CODE);
}
// Since this is a sample program,
// query method returns the following fixed result always without using database.
private static MatrixCursor sAddressCursor = new MatrixCursor(new String[] { "_id", "city" });
static {
sAddressCursor.addRow(new String[] { "1", "New York" });
sAddressCursor.addRow(new String[] { "2", "Longon" });
sAddressCursor.addRow(new String[] { "3", "Paris" });
}
private static MatrixCursor sDownloadCursor = new MatrixCursor(new String[] { "_id", "path" });
static {
sDownloadCursor.addRow(new String[] { "1", "/sdcard/downloads/sample.jpg" });
sDownloadCursor.addRow(new String[] { "2", "/sdcard/downloads/sample.txt" });
}
@Override
public boolean onCreate() {
return true;
}
@Override
public String getType(Uri uri) {
// *** POINT 3 *** Handle the received request data carefully and securely,
// even though the data comes from the same application.
// Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
// Checking for other parameters are omitted here, due to sample.
// Please refer to "3.2 Handle Input Data Carefully and Securely."
128
// *** POINT 4 *** Sensitive information can be sent since it is sending and receiving all within the same app
lication.
// However, the result of getType rarely has the sensitive meaning.
switch (sUriMatcher.match(uri)) {
case DOWNLOADS_CODE:
case ADDRESSES_CODE:
return CONTENT_TYPE;
case DOWNLOADS_ID_CODE:
case ADDRESSES_ID_CODE:
return CONTENT_ITEM_TYPE;
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
//
//
//
//
//
*** POINT 3 *** Handle the received request data carefully and securely,
even though the data comes from the same application.
Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
Checking for other parameters are omitted here, due to sample.
Please refer to "3.2 Handle Input Data Carefully and Securely."
// *** POINT 4 *** Sensitive information can be sent since it is sending and receiving all within the same app
lication.
// It depends on application whether the query result has sensitive meaning or not.
switch (sUriMatcher.match(uri)) {
case DOWNLOADS_CODE:
case DOWNLOADS_ID_CODE:
return sDownloadCursor;
case ADDRESSES_CODE:
case ADDRESSES_ID_CODE:
return sAddressCursor;
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {
//
//
//
//
//
*** POINT 3 *** Handle the received request data carefully and securely,
even though the data comes from the same application.
Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
Checking for other parameters are omitted here, due to sample.
Please refer to "3.2 Handle Input Data Carefully and Securely."
// *** POINT 4 *** Sensitive information can be sent since it is sending and receiving all within the same app
lication.
// It depends on application whether the issued ID has sensitive meaning or not.
switch (sUriMatcher.match(uri)) {
case DOWNLOADS_CODE:
return ContentUris.withAppendedId(Download.CONTENT_URI, 3);
129
case ADDRESSES_CODE:
return ContentUris.withAppendedId(Address.CONTENT_URI, 4);
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
//
//
//
//
//
*** POINT 3 *** Handle the received request data carefully and securely,
even though the data comes from the same application.
Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
Checking for other parameters are omitted here, due to sample.
Please refer to "3.2 Handle Input Data Carefully and Securely."
// *** POINT 4 *** Sensitive information can be sent since it is sending and receiving all within the same app
lication.
// It depends on application whether the number of updated records has sensitive meaning or not.
switch (sUriMatcher.match(uri)) {
case DOWNLOADS_CODE:
return 5; // Return number of updated records
case DOWNLOADS_ID_CODE:
return 1;
case ADDRESSES_CODE:
return 15;
case ADDRESSES_ID_CODE:
return 1;
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
//
//
//
//
//
*** POINT 3 *** Handle the received request data carefully and securely,
even though the data comes from the same application.
Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
Checking for other parameters are omitted here, due to sample.
Please refer to "3.2 Handle Input Data Carefully and Securely."
// *** POINT 4 *** Sensitive information can be sent since it is sending and receiving all within the same app
lication.
// It depends on application whether the number of deleted records has sensitive meaning or not.
switch (sUriMatcher.match(uri)) {
case DOWNLOADS_CODE:
return 10; // Return number of deleted records
case DOWNLOADS_ID_CODE:
return 1;
case ADDRESSES_CODE:
130
return 20;
case ADDRESSES_ID_CODE:
return 1;
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
}
}
131
Sensitive information can be sent since the destination provider is in the same application.
6.
Handle received result data carefully and securely, even though the data comes from the same
application.
PrivateUserActivity.java
package org.jssec.android.provider.privateprovider;
import
import
import
import
import
import
android.app.Activity;
android.database.Cursor;
android.net.Uri;
android.os.Bundle;
android.view.View;
android.widget.TextView;
*** POINT 6 *** Handle received result data carefully and securely,
even though the data comes from the same application.
Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
(cursor == null) {
logLine(" null cursor");
} else {
boolean moved = cursor.moveToFirst();
while (moved) {
logLine(String.format(" %d, %s", cursor.getInt(0), cursor.getString(1)));
moved = cursor.moveToNext();
}
}
}
finally {
if (cursor != null) cursor.close();
}
}
public void onInsertClick(View view) {
logLine("[Insert]");
// *** POINT 5 *** Sensitive information can be sent since the destination provider is in the same application
.
Uri uri = getContentResolver().insert(PrivateProvider.Download.CONTENT_URI, null);
// *** POINT 6 *** Handle received result data carefully and securely,
132
133
be attacked and tampered by Malware. For example, a saved data may be taken by select(), a data
may be changed by update(), or a fake data may be inserted/deleted by insert()/delete().
In addition, when using a custom Public Content Provider which is not provided by Android OS, it's
necessary to pay attention that request parameter may be received by Malware which masquerades
as the custom Public Content Provider, and also the attack result data may be sent. Contacts and
MediaStore provided by Android OS are also Public Content Providers, but Malware cannot
masquerades as them.
2.
AndroidManifest.xml
PublicProvider.java
package org.jssec.android.provider.publicprovider;
import
import
import
import
import
import
import
android.content.ContentProvider;
android.content.ContentUris;
android.content.ContentValues;
android.content.UriMatcher;
android.database.Cursor;
android.database.MatrixCursor;
android.net.Uri;
134
135
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
//
//
//
//
*** POINT 1 *** Handle the received request data carefully and securely.
Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
Checking for other parameters are omitted here, due to sample.
Refer to "3.2 Handle Input Data Carefully and Securely."
// *** POINT 2 *** When returning a result, do not include sensitive information.
// It depends on application whether the query result has sensitive meaning or not.
// If no problem when the information is taken by malware, it can be returned as result.
switch (sUriMatcher.match(uri)) {
case DOWNLOADS_CODE:
case DOWNLOADS_ID_CODE:
return sDownloadCursor;
case ADDRESSES_CODE:
case ADDRESSES_ID_CODE:
return sAddressCursor;
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {
//
//
//
//
*** POINT 1 *** Handle the received request data carefully and securely.
Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
Checking for other parameters are omitted here, due to sample.
Refer to "3.2 Handle Input Data Carefully and Securely."
// *** POINT 2 *** When returning a result, do not include sensitive information.
// It depends on application whether the issued ID has sensitive meaning or not.
// If no problem when the information is taken by malware, it can be returned as result.
switch (sUriMatcher.match(uri)) {
case DOWNLOADS_CODE:
return ContentUris.withAppendedId(Download.CONTENT_URI, 3);
case ADDRESSES_CODE:
return ContentUris.withAppendedId(Address.CONTENT_URI, 4);
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// *** POINT 1 *** Handle the received request data carefully and securely.
// Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
136
*** POINT 1 *** Handle the received request data carefully and securely.
Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
Checking for other parameters are omitted here, due to sample.
Refer to "3.2 Handle Input Data Carefully and Securely."
// *** POINT 2 *** When returning a result, do not include sensitive information.
// It depends on application whether the number of deleted records has sensitive meaning or not.
// If no problem when the information is taken by malware, it can be returned as result.
switch (sUriMatcher.match(uri)) {
case DOWNLOADS_CODE:
return 10; // Return number of deleted records
case DOWNLOADS_ID_CODE:
return 1;
case ADDRESSES_CODE:
return 20;
case ADDRESSES_ID_CODE:
return 1;
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
}
}
137
4.
When receiving a result, handle the result data carefully and securely.
PublicUserActivity.java
package org.jssec.android.provider.publicuser;
import
import
import
import
import
import
import
import
android.app.Activity;
android.content.ContentValues;
android.content.pm.ProviderInfo;
android.database.Cursor;
android.net.Uri;
android.os.Bundle;
android.view.View;
android.widget.TextView;
138
}
public void onInsertClick(View view) {
logLine("[Insert]");
if (!providerExists(Address.CONTENT_URI)) {
logLine(" Content Provider doesn't exist.");
return;
}
// *** POINT 3 *** Do not send sensitive information.
// since the target Content Provider may be malware.
// If no problem when the information is taken by malware, it can be included in the request.
ContentValues values = new ContentValues();
values.put("city", "Tokyo");
Uri uri = getContentResolver().insert(Address.CONTENT_URI, values);
// *** POINT 4 *** When receiving a result, handle the result data carefully and securely.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
logLine(" uri:" + uri);
}
public void onUpdateClick(View view) {
logLine("[Update]");
if (!providerExists(Address.CONTENT_URI)) {
logLine(" Content Provider doesn't exist.");
return;
}
// *** POINT 3 *** Do not send sensitive information.
// since the target Content Provider may be malware.
// If no problem when the information is taken by malware, it can be included in the request.
ContentValues values = new ContentValues();
values.put("city", "Tokyo");
String where = "_id = ?";
String[] args = { "4" };
int count = getContentResolver().update(Address.CONTENT_URI, values, where, args);
// *** POINT 4 *** When receiving a result, handle the result data carefully and securely.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
logLine(String.format(" %s records updated", count));
}
public void onDeleteClick(View view) {
logLine("[Delete]");
if (!providerExists(Address.CONTENT_URI)) {
logLine(" Content Provider doesn't exist.");
return;
}
// *** POINT 3 *** Do not send sensitive information.
// since the target Content Provider may be malware.
// If no problem when the information is taken by malware, it can be included in the request.
int count = getContentResolver().delete(Address.CONTENT_URI, null, null);
139
// *** POINT 4 *** When receiving a result, handle the result data carefully and securely.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
logLine(String.format(" %s records deleted", count));
}
private boolean providerExists(Uri uri) {
ProviderInfo pi = getPackageManager().resolveContentProvider(uri.getAuthority(), 0);
return (pi != null);
}
private TextView mLogView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mLogView = (TextView)findViewById(R.id.logview);
}
private void logLine(String line) {
mLogView.append(line);
mLogView.append("n");
}
}
140
protect the information and features which are handled between a partner application and an
In-house application.
Verify if the certificate of a requesting application has been registered in the own white list.
2.
Handle the received request data carefully and securely, even though the data comes from a
3.
partner application.
AndroidManifest.xml
PartnerProvider.java
package org.jssec.android.provider.partnerprovider;
import java.util.List;
import org.jssec.android.shared.PkgCertWhitelists;
import org.jssec.android.shared.Utils;
import
import
import
import
import
import
import
import
import
import
android.app.ActivityManager;
android.app.ActivityManager.RunningAppProcessInfo;
android.content.ContentProvider;
android.content.ContentUris;
android.content.ContentValues;
android.content.Context;
android.content.UriMatcher;
android.database.Cursor;
android.database.MatrixCursor;
android.net.Uri;
141
import android.os.Binder;
public class PartnerProvider extends ContentProvider {
public static final String AUTHORITY = "org.jssec.android.provider.partnerprovider";
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.org.jssec.contenttype";
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.org.jssec.contenttype";
// Expose the interface that the Content Provider provides.
public interface Download {
public static final String PATH = "downloads";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH);
}
public interface Address {
public static final String PATH = "addresses";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH);
}
// UriMatcher
private static final int DOWNLOADS_CODE = 1;
private static final int DOWNLOADS_ID_CODE = 2;
private static final int ADDRESSES_CODE = 3;
private static final int ADDRESSES_ID_CODE = 4;
private static UriMatcher sUriMatcher;
static {
sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
sUriMatcher.addURI(AUTHORITY, Download.PATH, DOWNLOADS_CODE);
sUriMatcher.addURI(AUTHORITY, Download.PATH + "/#", DOWNLOADS_ID_CODE);
sUriMatcher.addURI(AUTHORITY, Address.PATH, ADDRESSES_CODE);
sUriMatcher.addURI(AUTHORITY, Address.PATH + "/#", ADDRESSES_ID_CODE);
}
// Since this is a sample program,
// query method returns the following fixed result always without using database.
private static MatrixCursor sAddressCursor = new MatrixCursor(new String[] { "_id", "city" });
static {
sAddressCursor.addRow(new String[] { "1", "New York" });
sAddressCursor.addRow(new String[] { "2", "London" });
sAddressCursor.addRow(new String[] { "3", "Paris" });
}
private static MatrixCursor sDownloadCursor = new MatrixCursor(new String[] { "_id", "path" });
static {
sDownloadCursor.addRow(new String[] { "1", "/sdcard/downloads/sample.jpg" });
sDownloadCursor.addRow(new String[] { "2", "/sdcard/downloads/sample.txt" });
}
// *** POINT 1 *** Verify if the certificate of a requesting application has been registered in the own white lis
t.
private static PkgCertWhitelists sWhitelists = null;
private static void buildWhitelists(Context context) {
boolean isdebug = Utils.isDebuggable(context);
sWhitelists = new PkgCertWhitelists();
// Register certificate hash value of partner application org.jssec.android.provider.partneruser.
sWhitelists.add("org.jssec.android.provider.partneruser", isdebug ?
// Certificate hash value of "androiddebugkey" in the debug.keystore.
"0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255" :
// Certificate hash value of "partner key" in the keystore.
"1F039BB5 7861C27A 3916C778 8E78CE00 690B3974 3EB8259F E2627B8D 4C0EC35A");
142
*** POINT 2 *** Handle the received request data carefully and securely,
even though the data comes from a partner application.
Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
Checking for other parameters are omitted here, due to sample.
Refer to "3.2 Handle Input Data Carefully and Securely."
143
// *** POINT 3 *** Information that is granted to disclose to partner applications can be returned.
// It depends on application whether the query result can be disclosed or not.
switch (sUriMatcher.match(uri)) {
case DOWNLOADS_CODE:
case DOWNLOADS_ID_CODE:
return sDownloadCursor;
case ADDRESSES_CODE:
case ADDRESSES_ID_CODE:
return sAddressCursor;
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// *** POINT 1 *** Verify if the certificate of a requesting application has been registered in the own white
list.
if (!checkPartner(getContext(), getCallingPackage(getContext()))) {
throw new SecurityException("Calling application is not a partner application.");
}
//
//
//
//
//
*** POINT 2 *** Handle the received request data carefully and securely,
even though the data comes from a partner application.
Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
Checking for other parameters are omitted here, due to sample.
Refer to "3.2 Handle Input Data Carefully and Securely."
// *** POINT 3 *** Information that is granted to disclose to partner applications can be returned.
// It depends on application whether the issued ID has sensitive meaning or not.
switch (sUriMatcher.match(uri)) {
case DOWNLOADS_CODE:
return ContentUris.withAppendedId(Download.CONTENT_URI, 3);
case ADDRESSES_CODE:
return ContentUris.withAppendedId(Address.CONTENT_URI, 4);
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// *** POINT 1 *** Verify if the certificate of a requesting application has been registered in the own white
list.
if (!checkPartner(getContext(), getCallingPackage(getContext()))) {
throw new SecurityException("Calling application is not a partner application.");
}
//
//
//
//
144
*** POINT 2 *** Handle the received request data carefully and securely,
even though the data comes from a partner application.
Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
Checking for other parameters are omitted here, due to sample.
*** POINT 2 *** Handle the received request data carefully and securely,
even though the data comes from a partner application.
Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
Checking for other parameters are omitted here, due to sample.
Refer to "3.2 Handle Input Data Carefully and Securely."
// *** POINT 3 *** Information that is granted to disclose to partner applications can be returned.
// It depends on application whether the number of deleted records has sensitive meaning or not.
switch (sUriMatcher.match(uri)) {
case DOWNLOADS_CODE:
return 10; // Return number of deleted records
case DOWNLOADS_ID_CODE:
return 1;
case ADDRESSES_CODE:
return 20;
case ADDRESSES_ID_CODE:
return 1;
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
}
}
145
Verify if the certificate of the target application has been registered in the own white list.
6.
Handle the received result data carefully and securely, even though the data comes from a
5.
partner application.
PartnerActivity.java
package org.jssec.android.provider.partneruser;
import org.jssec.android.shared.PkgCertWhitelists;
import org.jssec.android.shared.Utils;
import
import
import
import
import
import
import
import
import
android.app.Activity;
android.content.ContentValues;
android.content.Context;
android.content.pm.ProviderInfo;
android.database.Cursor;
android.net.Uri;
android.os.Bundle;
android.view.View;
android.widget.TextView;
146
*** POINT 6 *** Handle the received result data carefully and securely,
even though the data comes from a partner application.
Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
(cursor == null) {
logLine(" null cursor");
} else {
boolean moved = cursor.moveToFirst();
while (moved) {
logLine(String.format(" %d, %s", cursor.getInt(0), cursor.getString(1)));
moved = cursor.moveToNext();
}
}
}
finally {
if (cursor != null) cursor.close();
}
}
public void onInsertClick(View view) {
logLine("[Insert]");
// *** POINT 4 *** Verify if the certificate of the target application has been registered in the own white li
st.
if (!checkPartner(this, providerPkgname(Address.CONTENT_URI))) {
logLine(" The target content provider is not served by partner applications.");
return;
}
// *** POINT 5 *** Information that is granted to disclose to partner applications can be sent.
ContentValues values = new ContentValues();
values.put("city", "Tokyo");
Uri uri = getContentResolver().insert(Address.CONTENT_URI, values);
// *** POINT 6 *** Handle the received result data carefully and securely,
// even though the data comes from a partner application.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
logLine(" uri:" + uri);
}
147
148
PkgCertWhitelists.java
package org.jssec.android.shared;
import java.util.HashMap;
import java.util.Map;
import android.content.Context;
public class PkgCertWhitelists {
private Map<String, String> mWhitelists = new HashMap<String, String>();
public boolean add(String pkgname, String sha256) {
if (pkgname == null) return false;
if (sha256 == null) return false;
sha256 = sha256.replaceAll(" ", "");
if (sha256.length() != 64) return false;
// SHA-256 -> 32 bytes -> 64 chars
sha256 = sha256.toUpperCase();
if (sha256.replaceAll("[0-9A-F]+", "").length() != 0) return false; // found non hex char
mWhitelists.put(pkgname, sha256);
return true;
}
public boolean test(Context ctx, String pkgname) {
// Get the correct hash value which corresponds to pkgname.
String correctHash = mWhitelists.get(pkgname);
// Compare the actual hash value of pkgname with the correct hash value.
return PkgCert.test(ctx, pkgname, correctHash);
}
}
PkgCert.java
package org.jssec.android.shared;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import
import
import
import
import
android.content.Context;
android.content.pm.PackageInfo;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
android.content.pm.Signature;
149
}
private static byte[] computeSha256(byte[] data) {
try {
return MessageDigest.getInstance("SHA-256").digest(data);
} catch (NoSuchAlgorithmException e) {
return null;
}
}
private static String byte2hex(byte[] data) {
if (data == null) return null;
final StringBuilder hexadecimal = new StringBuilder();
for (final byte b : data) {
hexadecimal.append(String.format("%02X", b));
}
return hexadecimal.toString();
}
}
150
Sample code of how to implement an In house only Content Provider is shown below.
Points (Creating a Content Provider):
1.
3.
5.
2.
4.
6.
Verify the safety of the parameter even if it's a request from In house only application.
When exporting an APK from Eclipse, sign the APK with the same developer key as that of the
requesting application.
AndroidManifest.xml
InhouseProvider.java
package org.jssec.android.provider.inhouseprovider;
import org.jssec.android.shared.SigPerm;
import org.jssec.android.shared.Utils;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
151
import android.content.Context;
import
import
import
import
android.content.UriMatcher;
android.database.Cursor;
android.database.MatrixCursor;
android.net.Uri;
152
} else {
// Certificate hash value of "my company key" in the keystore.
sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";
}
}
return sMyCertHash;
}
@Override
public boolean onCreate() {
return true;
}
@Override
public String getType(Uri uri) {
switch (sUriMatcher.match(uri)) {
case DOWNLOADS_CODE:
case ADDRESSES_CODE:
return CONTENT_TYPE;
case DOWNLOADS_ID_CODE:
case ADDRESSES_ID_CODE:
return CONTENT_ITEM_TYPE;
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// *** POINT 3 *** Verify if the in-house signature permission is defined by an in-house application.
if (!SigPerm.test(getContext(), MY_PERMISSION, myCertHash(getContext()))) {
throw new SecurityException("The in-house signature permission is not declared by in-house application.")
;
}
//
//
//
//
//
*** POINT 4 *** Handle the received request data carefully and securely,
even though the data came from an in-house application.
Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
Checking for other parameters are omitted here, due to sample.
Refer to "3.2 Handle Input Data Carefully and Securely."
// *** POINT 5 *** Sensitive information can be returned since the requesting application is in-house.
// It depends on application whether the query result has sensitive meaning or not.
switch (sUriMatcher.match(uri)) {
case DOWNLOADS_CODE:
case DOWNLOADS_ID_CODE:
return sDownloadCursor;
case ADDRESSES_CODE:
case ADDRESSES_ID_CODE:
return sAddressCursor;
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
153
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// *** POINT 3 *** Verify if the in-house signature permission is defined by an in-house application.
if (!SigPerm.test(getContext(), MY_PERMISSION, myCertHash(getContext()))) {
throw new SecurityException("The in-house signature permission is not declared by in-house application.")
;
}
//
//
//
//
//
*** POINT 4 *** Handle the received request data carefully and securely,
even though the data came from an in-house application.
Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
Checking for other parameters are omitted here, due to sample.
Refer to "3.2 Handle Input Data Carefully and Securely."
// *** POINT 5 *** Sensitive information can be returned since the requesting application is in-house.
// It depends on application whether the issued ID has sensitive meaning or not.
switch (sUriMatcher.match(uri)) {
case DOWNLOADS_CODE:
return ContentUris.withAppendedId(Download.CONTENT_URI, 3);
case ADDRESSES_CODE:
return ContentUris.withAppendedId(Address.CONTENT_URI, 4);
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// *** POINT 3 *** Verify if the in-house signature permission is defined by an in-house application.
if (!SigPerm.test(getContext(), MY_PERMISSION, myCertHash(getContext()))) {
throw new SecurityException("The in-house signature permission is not declared by in-house application.")
;
}
//
//
//
//
//
*** POINT 4 *** Handle the received request data carefully and securely,
even though the data came from an in-house application.
Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
Checking for other parameters are omitted here, due to sample.
Refer to "3.2 Handle Input Data Carefully and Securely."
// *** POINT 5 *** Sensitive information can be returned since the requesting application is in-house.
// It depends on application whether the number of updated records has sensitive meaning or not.
switch (sUriMatcher.match(uri)) {
case DOWNLOADS_CODE:
return 5; // Return number of updated records
case DOWNLOADS_ID_CODE:
return 1;
case ADDRESSES_CODE:
return 15;
case ADDRESSES_ID_CODE:
154
return 1;
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// *** POINT 3 *** Verify if the in-house signature permission is defined by an in-house application.
if (!SigPerm.test(getContext(), MY_PERMISSION, myCertHash(getContext()))) {
throw new SecurityException("The in-house signature permission is not declared by in-house application.")
;
}
//
//
//
//
//
*** POINT 4 *** Handle the received request data carefully and securely,
even though the data came from an in-house application.
Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
Checking for other parameters are omitted here, due to sample.
Refer to "3.2 Handle Input Data Carefully and Securely."
// *** POINT 5 *** Sensitive information can be returned since the requesting application is in-house.
// It depends on application whether the number of deleted records has sensitive meaning or not.
switch (sUriMatcher.match(uri)) {
case DOWNLOADS_CODE:
return 10; // Return number of deleted records
case DOWNLOADS_ID_CODE:
return 1;
case ADDRESSES_CODE:
return 20;
case ADDRESSES_ID_CODE:
return 1;
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
}
}
SigPerm.java
package org.jssec.android.shared;
import
import
import
import
android.content.Context;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
android.content.pm.PermissionInfo;
155
PkgCert.java
package org.jssec.android.shared;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import
import
import
import
import
android.content.Context;
android.content.pm.PackageInfo;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
android.content.pm.Signature;
156
try {
return MessageDigest.getInstance("SHA-256").digest(data);
} catch (NoSuchAlgorithmException e) {
return null;
}
}
private static String byte2hex(byte[] data) {
if (data == null) return null;
final StringBuilder hexadecimal = new StringBuilder();
for (final byte b : data) {
hexadecimal.append(String.format("%02X", b));
}
return hexadecimal.toString();
}
}
*** Point 6 *** When exporting an APK from Eclipse, sign the APK with the same developer key as the
requesting application.
Figure 4.3-2
157
Next is the example of Activity which uses In house only Content Provider.
Point (Using a Content Provider):
7.
9.
8.
10. Sensitive information can be sent since the destination application is in-house one.
11. Handle the received result data carefully and securely, even though the data comes from an
in-house application.
12. When exporting an APK from Eclipse, sign the APK with the same developer key as that of the
destination application.
AndroidManifest.xml
InhouseUserActivity.java
package org.jssec.android.provider.inhouseuser;
import org.jssec.android.shared.PkgCert;
import org.jssec.android.shared.SigPerm;
import org.jssec.android.shared.Utils;
import
import
import
import
import
import
import
import
158
android.app.Activity;
android.content.ContentValues;
android.content.Context;
android.content.pm.PackageManager;
android.content.pm.ProviderInfo;
android.database.Cursor;
android.net.Uri;
android.os.Bundle;
import android.view.View;
import android.widget.TextView;
public class InhouseUserActivity extends Activity {
// Target Content Provider Information
private static final String AUTHORITY = "org.jssec.android.provider.inhouseprovider";
private interface Address {
public static final String PATH = "addresses";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH);
}
// In-house Signature Permission
private static final String MY_PERMISSION = "org.jssec.android.provider.inhouseprovider.MY_PERMISSION";
// In-house certificate hash value
private static String sMyCertHash = null;
private static String myCertHash(Context context) {
if (sMyCertHash == null) {
if (Utils.isDebuggable(context)) {
// Certificate hash value of "androiddebugkey" in the debug.keystore.
sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255";
} else {
// Certificate hash value of "my company key" in the keystore.
sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";
}
}
return sMyCertHash;
}
// Get package name of target content provider.
private static String providerPkgname(Context context, Uri uri) {
String pkgname = null;
PackageManager pm = context.getPackageManager();
ProviderInfo pi = pm.resolveContentProvider(uri.getAuthority(), 0);
if (pi != null) pkgname = pi.packageName;
return pkgname;
}
public void onQueryClick(View view) {
logLine("[Query]");
// *** POINT 8 *** Verify if the in-house signature permission is defined by an in-house application.
if (!SigPerm.test(this, MY_PERMISSION, myCertHash(this))) {
logLine(" The in-house signature permission is not declared by in-house application.");
return;
}
// *** POINT 9 *** Verify if the destination application is signed with the in-house certificate.
String pkgname = providerPkgname(this, Address.CONTENT_URI);
if (!PkgCert.test(this, pkgname, myCertHash(this))) {
logLine(" The target content provider is not served by in-house applications.");
return;
}
Cursor cursor = null;
try {
// *** POINT 10 *** Sensitive information can be sent since the destination application is in-house one.
cursor = getContentResolver().query(Address.CONTENT_URI, null, null, null, null);
159
//
//
//
if
*** POINT 11 *** Handle the received result data carefully and securely,
even though the data comes from an in-house application.
Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
(cursor == null) {
logLine(" null cursor");
} else {
boolean moved = cursor.moveToFirst();
while (moved) {
logLine(String.format(" %d, %s", cursor.getInt(0), cursor.getString(1)));
moved = cursor.moveToNext();
}
}
}
finally {
if (cursor != null) cursor.close();
}
}
public void onInsertClick(View view) {
logLine("[Insert]");
// *** POINT 8 *** Verify if the in-house signature permission is defined by an in-house application.
String correctHash = myCertHash(this);
if (!SigPerm.test(this, MY_PERMISSION, correctHash)) {
logLine(" The in-house signature permission is not declared by in-house application.");
return;
}
// *** POINT 9 *** Verify if the destination application is signed with the in-house certificate.
String pkgname = providerPkgname(this, Address.CONTENT_URI);
if (!PkgCert.test(this, pkgname, correctHash)) {
logLine(" The target content provider is not served by in-house applications.");
return;
}
// *** POINT 10 *** Sensitive information can be sent since the destination application is in-house one.
ContentValues values = new ContentValues();
values.put("city", "Tokyo");
Uri uri = getContentResolver().insert(Address.CONTENT_URI, values);
// *** POINT 11 *** Handle the received result data carefully and securely,
// even though the data comes from an in-house application.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
logLine(" uri:" + uri);
}
public void onUpdateClick(View view) {
logLine("[Update]");
// *** POINT 8 *** Verify if the in-house signature permission is defined by an in-house application.
String correctHash = myCertHash(this);
if (!SigPerm.test(this, MY_PERMISSION, correctHash)) {
logLine(" The in-house signature permission is not declared by in-house application.");
return;
}
// *** POINT 9 *** Verify if the destination application is signed with the in-house certificate.
160
161
SigPerm.java
package org.jssec.android.shared;
import
import
import
import
android.content.Context;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
android.content.pm.PermissionInfo;
PkgCert.java
package org.jssec.android.shared;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import
import
import
import
import
android.content.Context;
android.content.pm.PackageInfo;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
android.content.pm.Signature;
162
*** Point 12 *** When exporting an APK from Eclipse, sign the APK with the same developer key as
that of the destination application.
Figure 4.3-3
163
particular applications to access the particular URI. By sending an Intent which special flag is
specified to the target applications, temporary access permission is provided to those applications.
Contents provider side application can give the access permission actively to other applications, and
it can also give access permission passively to the application which claims the temporary access
permission.
Sample code of how to implement a temporary permit Content Provider is shown below.
Points (Creating a Content Provider):
1.
Do not (Cannot) implement temporary permit content provider in Android 2.2 (API Level 8) or
earlier.
2.
3.
4.
Handle the received request data carefully and securely, even though the data comes from the
5.
Information that is granted to disclose to the temporary access applications can be returned.
6.
7.
8.
9.
AndroidManifest.xml
164
android:name=".TemporaryProvider"
android:authorities="org.jssec.android.provider.temporaryprovider"
android:exported="false" >
<!-- *** POINT 3 *** Specify the path to grant access temporarily with the grant-uri-permission. -->
<grant-uri-permission android:path="/addresses" />
</provider>
<activity
android:name=".TemporaryPassiveGrantActivity"
android:label="@string/app_name"
android:exported="true" />
</application>
</manifest>
TemporaryProvider.java
package org.jssec.android.provider.temporaryprovider;
import
import
import
import
import
import
import
android.content.ContentProvider;
android.content.ContentUris;
android.content.ContentValues;
android.content.UriMatcher;
android.database.Cursor;
android.database.MatrixCursor;
android.net.Uri;
165
private static MatrixCursor sAddressCursor = new MatrixCursor(new String[] { "_id", "city" });
static {
sAddressCursor.addRow(new String[] { "1", "New York" });
sAddressCursor.addRow(new String[] { "2", "London" });
sAddressCursor.addRow(new String[] { "3", "Paris" });
}
private static MatrixCursor sDownloadCursor = new MatrixCursor(new String[] { "_id", "path" });
static {
sDownloadCursor.addRow(new String[] { "1", "/sdcard/downloads/sample.jpg" });
sDownloadCursor.addRow(new String[] { "2", "/sdcard/downloads/sample.txt" });
}
@Override
public boolean onCreate() {
return true;
}
@Override
public String getType(Uri uri) {
switch (sUriMatcher.match(uri)) {
case DOWNLOADS_CODE:
case ADDRESSES_CODE:
return CONTENT_TYPE;
case DOWNLOADS_ID_CODE:
case ADDRESSES_ID_CODE:
return CONTENT_ITEM_TYPE;
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
//
//
//
//
//
*** POINT 4 *** Handle the received request data carefully and securely,
even though the data comes from the application granted access temporarily.
Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
Checking for other parameters are omitted here, due to sample.
Please refer to "3.2 Handle Input Data Carefully and Securely."
// *** POINT 5 *** Information that is granted to disclose to the temporary access applications can be returne
d.
// It depends on application whether the query result can be disclosed or not.
switch (sUriMatcher.match(uri)) {
case DOWNLOADS_CODE:
case DOWNLOADS_ID_CODE:
return sDownloadCursor;
case ADDRESSES_CODE:
case ADDRESSES_ID_CODE:
return sAddressCursor;
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
}
166
@Override
public Uri insert(Uri uri, ContentValues values) {
//
//
//
//
//
*** POINT 4 *** Handle the received request data carefully and securely,
even though the data comes from the application granted access temporarily.
Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
Checking for other parameters are omitted here, due to sample.
Please refer to "3.2 Handle Input Data Carefully and Securely."
// *** POINT 5 *** Information that is granted to disclose to the temporary access applications can be returne
d.
// It depends on application whether the issued ID has sensitive meaning or not.
switch (sUriMatcher.match(uri)) {
case DOWNLOADS_CODE:
return ContentUris.withAppendedId(Download.CONTENT_URI, 3);
case ADDRESSES_CODE:
return ContentUris.withAppendedId(Address.CONTENT_URI, 4);
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
//
//
//
//
//
*** POINT 4 *** Handle the received request data carefully and securely,
even though the data comes from the application granted access temporarily.
Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
Checking for other parameters are omitted here, due to sample.
Please refer to "3.2 Handle Input Data Carefully and Securely."
// *** POINT 5 *** Information that is granted to disclose to the temporary access applications can be returne
d.
// It depends on application whether the number of updated records has sensitive meaning or not.
switch (sUriMatcher.match(uri)) {
case DOWNLOADS_CODE:
return 5; // Return number of updated records
case DOWNLOADS_ID_CODE:
return 1;
case ADDRESSES_CODE:
return 15;
case ADDRESSES_ID_CODE:
return 1;
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// *** POINT 4 *** Handle the received request data carefully and securely,
167
// even though the data comes from the application granted access temporarily.
// Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
// Checking for other parameters are omitted here, due to sample.
// Please refer to "3.2 Handle Input Data Carefully and Securely."
// *** POINT 5 *** Information that is granted to disclose to the temporary access applications can be returne
d.
// It depends on application whether the number of deleted records has sensitive meaning or not.
switch (sUriMatcher.match(uri)) {
case DOWNLOADS_CODE:
return 10; // Return number of deleted records
case DOWNLOADS_ID_CODE:
return 1;
case ADDRESSES_CODE:
return 20;
case ADDRESSES_ID_CODE:
return 1;
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
}
}
TemporaryActiveGrantActivity.java
package org.jssec.android.provider.temporaryprovider;
import
import
import
import
import
import
android.app.Activity;
android.content.ActivityNotFoundException;
android.content.Intent;
android.os.Bundle;
android.view.View;
android.widget.Toast;
168
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// *** POINT 8 *** Send the explicit intent to an application to grant temporary access.
intent.setClassName(TARGET_PACKAGE, TARGET_ACTIVITY);
startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, "User Activity not found.", Toast.LENGTH_LONG).show();
}
}
}
TemporaryPassiveGrantActivity.java
package org.jssec.android.provider.temporaryprovider;
import
import
import
import
android.app.Activity;
android.content.Intent;
android.os.Bundle;
android.view.View;
169
11. When receiving a result, handle the result data carefully and securely.
TemporaryUserActivity.java
package org.jssec.android.provider.temporaryuser;
import
import
import
import
import
import
import
import
import
android.app.Activity;
android.content.ActivityNotFoundException;
android.content.Intent;
android.content.pm.ProviderInfo;
android.database.Cursor;
android.net.Uri;
android.os.Bundle;
android.view.View;
android.widget.TextView;
170
moved = cursor.moveToNext();
}
}
} catch (SecurityException ex) {
logLine(" Exception:" + ex.getMessage());
}
finally {
if (cursor != null) cursor.close();
}
}
// In the case that this application requests temporary access to the Content Provider
// and the Content Provider passively grants temporary access permission to this application.
public void onGrantRequestClick(View view) {
Intent intent = new Intent();
intent.setClassName(TARGET_PACKAGE, TARGET_ACTIVITY);
try {
startActivityForResult(intent, REQUEST_CODE);
} catch (ActivityNotFoundException e) {
logLine("Content Provider's Activity not found.");
}
}
private boolean providerExists(Uri uri) {
ProviderInfo pi = getPackageManager().resolveContentProvider(uri.getAuthority(), 0);
return (pi != null);
}
private TextView mLogView;
// In the case that the Content Provider application grants temporary access
// to this application actively.
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mLogView = (TextView)findViewById(R.id.logview);
}
private void logLine(String line) {
mLogView.append(line);
mLogView.append("n");
}
}
171
Content Provider that Is Used Only in an Application Can Not Be Created in Android 2.2 (API Level
2.
4.
Use an In-house Defined Signature Permission after Verifying that it is Defined by an In-house
3.
8) or Earlier
(Required)
(Required)
(Required)
Application
(Required)
(Required)
Protection
(Required)
5.
When Returning a Result, Pay Attention to the Possibility of Information Leakage of that Result
6.
When Providing an Asset Secondarily, the Asset should be Protected with the Same Level of
Handle the Returned Result Data from the Content Provider Carefully and Securely
(Required)
4.3.2.1. Content Provider that Is Used Only in an Application Can Not Be Created in Android 2.2 (API
Level 8) or Earlier
(Required)
Private setting for a Content Provider does not work in Android 2.2 (API Level 8) or earlier. To share a
data in the same application, access a data storage location such as a data base instead of using a
Content Provider.
4.3.2.2. Content Provider that Is Used Only in an Application Must Be Set as Private
(Required)
Content Provider which is used only in a single application is not necessary to be accessed by other
applications, and the access which attacks the Content Provider is not often considered by
developers. A Content Provider is basically the system to share data, so it's handled as public by
default. A Content Provider which is used only in a single application should be set as private
explicitly, and it should be a private Content Provider. In Android 2.3.1 (API Level 9) or later, a
Content Provider can be set as private by specifying android:exported="false" in provider element.
AndroidManifest.xml
<!-- *** POINT 1 *** Do not (Cannot) implement Private Content Provider in Android 2.2 (API Level 8) or earlier.
-->
<uses-sdk android:minSdkVersion="9" />
-abbreviation<!-- *** POINT 2 *** Set false for the exported attribute explicitly. -->
<provider
android:name=".PrivateProvider"
android:authorities="org.jssec.android.provider.privateprovider"
android:exported="false" />
172
(Required)
Risks differ depending on the types of Content Providers, but when processing request parameters,
the first thing you should do is input validation.
Although each method of a Content Provider has the interface which is supposed to receive the
component parameter of SQL statement, actually it simply hands over the arbitrary character string in
the system, so it's necessary to pay attention that Contents Provider side needs to suppose the case
Since Public Content Providers can receive requests from untrusted sources, they can be attacked by
malware. On the other hand, Private Content Providers will never receive any requests from other
applications directly, but it is possible that a Public Activity in the targeted application may forward a
malicious Intent to a Private Content Provider so you should not assume that Private Content
Providers cannot receive any malicious input.
Since other Content Providers also have the risk of a malicious intent being forwarded to them as well,
it is necessary to perform input validation on these requests as well.
Please refer to "3.2 Handling Input Data Carefully and Securely"
4.3.2.4. Use an In-house Defined Signature Permission after Verifying that it is Defined by an In-house
Application
(Required)
Make sure to protect your in-house Content Providers by defining an in-house signature permission
when creating the Content Provider. Since defining a permission in the AndroidManifest.xml file or
declaring a permission request does not provide adequate security, please be sure to refer to "5.2.1.2
4.3.2.5. When Returning a Result, Pay Attention to the Possibility of Information Leakage of that Result
from the Destination Application
(Required)
In case of query() or insert(), Cursor or Uri is returned to the request sending application as a result
information. When sensitive information is included in the result information, the information may be
leaked from the destination application. In case of update() or delete(), number of updated/deleted
records is returned to the request sending application as a result information. In rare cases,
depending on some application specs, the number of updated/deleted records has the sensitive
meaning, so please pay attention to this.
4.3.2.6. When Providing an Asset Secondarily, the Asset should be Protected with the Same Level of
Protection
(Required)
application secondhand, you need to make sure that it has the same required permissions needed to
4.3 Creating/Using Content Providers
173
access the asset. In the Android OS permission security model, only an application that has been
granted proper permissions can directly access a protected asset. However, there is a loophole
because an application with permissions to an asset can act as a proxy and allow access to an
4.3.2.7. Handle the Returned Result Data from the Content Provider Carefully and Securely
(Required)
Risks differ depending on the types of Content Provider, but when processing a result data, the first
thing you should do is input validation.
In case that the destination Content Provider is a public Content Provider, Malware which
masquerades as the public Content Provider may return the attack result data. On the other hand, in
case that the destination Content Provider is a private Content Provider, it is less risk because it
receives the result data from the same application, but you should not assume that private Content
Providers cannot receive any malicious input.Since other Content Providers also have the risk of a
malicious data being returned to them as well, it is necessary to perform input validation on that
result data as well.
174
http://www.jssec.org/dl/android_securecoding_en.pdf
below. Since the secure coding best practice varies according to how the service is created, we will
Definition
Private Service
Public Service
Partner Service
In-house Service
number of applications
Start
Yes
No
Use only in
the same application?
Yes
No
Yes
Private Service
Public Service
Partner Service
No
In-house Service
Figure 4.4-1
There are several implementation methods for Service, and you will select the method which matches
with the type of Service that you suppose to create. The items of vertical columns in the table show
the implementation methods, and these are divided into 5 types. "OK" stands for the possible
Please refer to "4.4.3.2 How to Implement Service" and Sample code of each Service type (with * mark
175
http://www.jssec.org/dl/android_securecoding_en.pdf
Category
Private
Service
Public
Service
Partner
Service
In-house
Service
OK
OK*
OK
OK
OK
OK*
OK
OK
OK*
OK
startService type
OK*
OK
IntentService type
Messenger
type
bind
Table 4.4-2
OK
-
OK
-
Sample code for each security type of Service are shown as below, by using combination of * mark in
Table 4.4-2.
176
When using Private Services that are only used within the application, as long as you use explicit
Intents to the class then you do not have to worry about accidently sending it to any other
application.
Sample code of how to use the startService type Service is shown below.
Points (Creating a Service):
1.
2.
Handle the received intent carefully and securely, even though the intent was sent from the same
3.
Sensitive information can be sent since the requesting application is in the same application.
application.
AndroidManifest.xml
PrivateStartService.java
package org.jssec.android.service.privateservice;
177
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.widget.Toast;
public class PrivateStartService extends Service {
// The onCreate gets called only one time when the service starts.
@Override
public void onCreate() {
Toast.makeText(this, "PrivateStartService - onCreate()", Toast.LENGTH_SHORT).show();
}
// The onStartCommand gets called each time after the startService gets called.
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// *** POINT 2 *** Handle the received intent carefully and securely,
// even though the intent was sent from the same application.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
String param = intent.getStringExtra("PARAM");
Toast.makeText(this,
String.format("PrivateStartServicenReceived param: "%s"", param),
Toast.LENGTH_LONG).show();
return Service.START_NOT_STICKY;
}
// The onDestroy gets called only one time when the service stops.
@Override
public void onDestroy() {
Toast.makeText(this, "PrivateStartService - onDestroy()", Toast.LENGTH_SHORT).show();
}
@Override
public IBinder onBind(Intent intent) {
// This service does not provide binding, so return null
return null;
}
}
178
Use the explicit intent with class specified to call a service in the same application.
6.
Handle the received result data carefully and securely, even though the data came from a service
5.
Sensitive information can be sent since the destination service is in the same application.
PrivateUserActivity.java
package org.jssec.android.service.privateservice;
import
import
import
import
android.app.Activity;
android.content.Intent;
android.os.Bundle;
android.view.View;
179
180
sent by Malware. In case using public Service, It's necessary to pay attention that information(Intent
etc.) to send may be received by Malware.
Sample code of how to use the startService type Service is shown below.
Points (Creating a Service):
1.
2.
AndroidManifest.xml
PublicIntentService.java
package org.jssec.android.service.publicservice;
import android.app.IntentService;
import android.content.Intent;
import android.widget.Toast;
public class PublicIntentService extends IntentService{
181
/**
* Default constructor must be provided when a service extends IntentService class.
* If it does not exist, an error occurs.
*/
public PublicIntentService() {
super("CreatingTypeBService");
}
// The onCreate gets called only one time when the Service starts.
@Override
public void onCreate() {
super.onCreate();
Toast.makeText(this, this.getClass().getSimpleName() + " - onCreate()", Toast.LENGTH_SHORT).show();
}
// The onHandleIntent gets called each time after the startService gets called.
@Override
protected void onHandleIntent(Intent intent) {
// *** POINT 1 *** Handle intent carefully and securely.
// Since it's public service, the intent may come from malicious application.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
String param = intent.getStringExtra("PARAM");
Toast.makeText(this, String.format("Recieved parameter "%s"", param), Toast.LENGTH_LONG).show();
}
// The onDestroy gets called only one time when the service stops.
@Override
public void onDestroy() {
Toast.makeText(this, this.getClass().getSimpleName() + " - onDestroy()", Toast.LENGTH_SHORT).show();
}
}
182
4.
When receiving a result, handle the result data carefully and securely.
AndroidManifest.xml
PublicUserActivity.java
package org.jssec.android.service.publicserviceuser;
import org.jssec.android.service.publicserviceuser.R;
import
import
import
import
android.app.Activity;
android.content.Intent;
android.os.Bundle;
android.view.View;
183
184
partner company's application and In house application, this is used to protect the information and
features which are handled between a partner application and In house application.
Following is an example of AIDL bind type Service.
Points (Creating a Service):
1.
3.
Do not (Cannot) recognize whether the requesting application is partner or not by onBind
2.
Verify that the certificate of the requesting application has been registered in the own white list.
(onStartCommand, onHandleIntent).
4.
Handle the received intent carefully and securely, even though the intent was sent from a partner
5.
application.
In addition, refer to "5.2.1.3 How to verify the hash value of an application's certificate" for how to
verify the certification hash value of destination application which is specified to white list.
AndroidManifest.xml
In this example, 2 AIDL files are to be created. One is for callback interface to give data from Service
to Activity. The other one is Interface to give data from Activity to Service and to get information. In
addition, package name that is described in AIDL file should be consistent with directory hierarchy in
which AIDL file is created, same like package name described in java file.
IExclusiveAIDLServiceCallback.aidl
package org.jssec.android.service.exclusiveservice.aidl;
185
interface IExclusiveAIDLServiceCallback {
/**
* It's called when the value is changed.
*/
void valueChanged(String info);
}
IExclusiveAIDLService.aidl
package org.jssec.android.service.exclusiveservice.aidl;
import org.jssec.android.service.exclusiveservice.aidl.IExclusiveAIDLServiceCallback;
interface IExclusiveAIDLService {
/**
* Register Callback.
*/
void registerCallback(IExclusiveAIDLServiceCallback cb);
/**
* Get Information
*/
String getInfo(String param);
/**
* Unregister Callback
*/
void unregisterCallback(IExclusiveAIDLServiceCallback cb);
}
PartnerAIDLService.java
package org.jssec.android.service.partnerservice.aidl;
import
import
import
import
org.jssec.android.service.partnerservice.aidl.IPartnerAIDLService;
org.jssec.android.service.partnerservice.aidl.IPartnerAIDLServiceCallback;
org.jssec.android.shared.PkgCertWhitelists;
org.jssec.android.shared.Utils;
import
import
import
import
import
import
import
import
import
android.app.Service;
android.content.Context;
android.content.Intent;
android.os.Handler;
android.os.IBinder;
android.os.Message;
android.os.RemoteCallbackList;
android.os.RemoteException;
android.widget.Toast;
186
187
default:
super.handleMessage(msg);
break;
} // switch
}
};
// Interfaces defined in AIDL
private final IPartnerAIDLService.Stub mBinder = new IPartnerAIDLService.Stub() {
private final boolean checkPartner() {
Context ctx = PartnerAIDLService.this;
if (!PartnerAIDLService.checkPartner(ctx, Utils.getPackageNameFromPid(ctx, getCallingPid()))) {
Toast.makeText(ctx, "Requesting application is not partner application.", Toast.LENGTH_LONG).show();
return false;
}
return true;
}
public void registerCallback(IPartnerAIDLServiceCallback cb) {
// *** POINT 2 *** Verify that the certificate of the requesting application has been registered in the ow
n white list.
if (!checkPartner()) {
return;
}
if (cb != null) mCallbacks.register(cb);
}
public String getInfo(String param) {
// *** POINT 2 *** Verify that the certificate of the requesting application has been registered in the ow
n white list.
if (!checkPartner()) {
return null;
}
// *** POINT 4 *** Handle the received intent carefully and securely,
// even though the intent was sent from a partner application
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
Message msg = new Message();
msg.what = GETINFO_MSG;
msg.obj = String.format("Method calling from partner application. Recieved "%s"", param);
PartnerAIDLService.this.mHandler.sendMessage(msg);
// *** POINT 5 *** Return only information that is granted to be disclosed to a partner application.
return new String("Information disclosed to partner application (method from Service)");
}
public void unregisterCallback(IPartnerAIDLServiceCallback cb) {
// *** POINT 2 *** Verify that the certificate of the requesting application has been registered in the ow
n white list.
if (!checkPartner()) {
return;
}
if (cb != null) mCallbacks.unregister(cb);
}
};
@Override
public IBinder onBind(Intent intent) {
// *** POINT 2 *** Verify that the certificate of the requesting application has been registered in the own wh
ite list.
// So requesting application must be validated in methods defined in AIDL every time.
return mBinder;
188
}
@Override
public void onCreate() {
Toast.makeText(this, this.getClass().getSimpleName() + " - onCreate()", Toast.LENGTH_SHORT).show();
// During service is running, inform the incremented number periodically.
mHandler.sendEmptyMessage(REPORT_MSG);
}
@Override
public void onDestroy() {
Toast.makeText(this, this.getClass().getSimpleName() + " - onDestroy()", Toast.LENGTH_SHORT).show();
// Unregister all callbacks
mCallbacks.kill();
mHandler.removeMessages(REPORT_MSG);
}
}
PkgCertWhitelists.java
package org.jssec.android.shared;
import java.util.HashMap;
import java.util.Map;
import android.content.Context;
public class PkgCertWhitelists {
private Map<String, String> mWhitelists = new HashMap<String, String>();
public boolean add(String pkgname, String sha256) {
if (pkgname == null) return false;
if (sha256 == null) return false;
sha256 = sha256.replaceAll(" ", "");
if (sha256.length() != 64) return false;
// SHA-256 -> 32 bytes -> 64 chars
sha256 = sha256.toUpperCase();
if (sha256.replaceAll("[0-9A-F]+", "").length() != 0) return false; // found non hex char
mWhitelists.put(pkgname, sha256);
return true;
}
public boolean test(Context ctx, String pkgname) {
// Get the correct hash value which corresponds to pkgname.
String correctHash = mWhitelists.get(pkgname);
// Compare the actual hash value of pkgname with the correct hash value.
return PkgCert.test(ctx, pkgname, correctHash);
}
}
PkgCert.java
package org.jssec.android.shared;
189
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import
import
import
import
import
android.content.Context;
android.content.pm.PackageInfo;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
android.content.pm.Signature;
190
Verify if the certificate of the target application has been registered in the own white list.
8.
7.
9.
Handle the received result data carefully and securely, even though the data came from a partner
application.
ExclusiveAIDLUserActivity.java
package org.jssec.android.service.partnerservice.aidluser;
import
import
import
import
import
org.jssec.android.service.partnerservice.aidl.IPartnerAIDLService;
org.jssec.android.service.partnerservice.aidl.IPartnerAIDLServiceCallback;
org.jssec.android.service.partnerservice.aidluser.R;
org.jssec.android.shared.PkgCertWhitelists;
org.jssec.android.shared.Utils;
import
import
import
import
import
import
import
import
import
import
import
import
android.app.Activity;
android.content.ComponentName;
android.content.Context;
android.content.Intent;
android.content.ServiceConnection;
android.os.Bundle;
android.os.Handler;
android.os.IBinder;
android.os.Message;
android.os.RemoteException;
android.view.View;
android.widget.Toast;
191
}
// Information about destination (requested) partner activity.
private static final String TARGET_PACKAGE = "org.jssec.android.service.partnerservice.aidl";
private static final String TARGET_CLASS = "org.jssec.android.service.partnerservice.aidl.partnerAIDLService";
private final Handler mHandler = new Handler() {
@Override public void handleMessage(Message msg) {
switch (msg.what) {
case MGS_VALUE_CHANGED: {
String info = (String)msg.obj;
Toast.makeText(mContext, String.format("Received "%s" with callback.", info), Toast.LENGTH_SHORT
).show();
break;
}
default:
super.handleMessage(msg);
break;
} // switch
}
};
// Interfaces defined in AIDL. Receive notice from service
private final IPartnerAIDLServiceCallback.Stub mCallback =
new IPartnerAIDLServiceCallback.Stub() {
@Override
public void valueChanged(String info) throws RemoteException {
Message msg = mHandler.obtainMessage(MGS_VALUE_CHANGED, info);
mHandler.sendMessage(msg);
}
};
// Interfaces defined in AIDL. Inform service.
private IPartnerAIDLService mService = null;
// Connection used to connect with service. This is necessary when service is implemented with bindService().
private ServiceConnection mConnection = new ServiceConnection() {
// This is called when the connection with the service has been established.
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
mService = IPartnerAIDLService.Stub.asInterface(service);
try{
// connect to service
mService.registerCallback(mCallback);
}catch(RemoteException e){
// service stopped abnormally
}
Toast.makeText(mContext, "Connected to service", Toast.LENGTH_SHORT).show();
}
// This is called when the service stopped abnormally and connection is disconnected.
@Override
public void onServiceDisconnected(ComponentName className) {
Toast.makeText(mContext, "Disconnected from service", Toast.LENGTH_SHORT).show();
}
};
192
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.partnerservice_activity);
mContext = this;
}
// --- StartService control --public void onStartServiceClick(View v) {
// Start bindService
doBindService();
}
public void onGetInfoClick(View v) {
getServiceinfo();
}
public void onStopServiceClick(View v) {
doUnbindService();
}
@Override
public void onDestroy() {
super.onDestroy();
doUnbindService();
}
/**
* Connect to service
*/
private void doBindService() {
if (!mIsBound){
// *** POINT 6 *** Verify if the certificate of the target application has been registered in the own whit
e list.
if (!checkPartner(this, TARGET_PACKAGE)) {
Toast.makeText(this, "Destination(Requested) sevice application is not registered in white list.", Toa
st.LENGTH_LONG).show();
return;
}
Intent intent = new Intent();
// *** POINT 7 *** Return only information that is granted to be disclosed to a partner application.
intent.putExtra("PARAM", "Information disclosed to partner application");
// *** POINT 8 *** Use the explicit intent to call a partner service.
intent.setClassName(TARGET_PACKAGE, TARGET_CLASS);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
}
/**
* Disconnect service
*/
193
PkgCertWhitelists.java
package org.jssec.android.shared;
import java.util.HashMap;
import java.util.Map;
import android.content.Context;
public class PkgCertWhitelists {
private Map<String, String> mWhitelists = new HashMap<String, String>();
194
PkgCert.java
package org.jssec.android.shared;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import
import
import
import
import
android.content.Context;
android.content.pm.PackageInfo;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
android.content.pm.Signature;
195
try {
return MessageDigest.getInstance("SHA-256").digest(data);
} catch (NoSuchAlgorithmException e) {
return null;
}
}
private static String byte2hex(byte[] data) {
if (data == null) return null;
final StringBuilder hexadecimal = new StringBuilder();
for (final byte b : data) {
hexadecimal.append(String.format("%02X", b));
}
return hexadecimal.toString();
}
}
196
in-house applications. They are used in applications developed internally that want to securely share
information and functionality.
3.
2.
4.
5.
Handle the received intent carefully and securely, even though the intent was sent from an
6.
7.
in-house application.
When exporting an APK from Eclipse, sign the APK with the same developer key as the requesting
application.
AndroidManifest.xml
InhouseMessengerService.java
package org.jssec.android.service.inhouseservice.messenger;
197
import org.jssec.android.shared.SigPerm;
import org.jssec.android.shared.Utils;
import java.util.ArrayList;
import java.util.Iterator;
import
import
import
import
import
import
import
import
import
import
android.app.Service;
android.content.Context;
android.content.Intent;
android.os.Bundle;
android.os.Handler;
android.os.IBinder;
android.os.Message;
android.os.Messenger;
android.os.RemoteException;
android.widget.Toast;
198
super.handleMessage(msg);
break;
}
}
}
/**
* Send data to client
*/
private void sendMessageToClients(){
// *** POINT 6 *** Sensitive information can be returned since the requesting application is in-house.
String sendValue = "Sensitive information (from Service)";
// Send data to the registered client one by one.
// Use iterator to send all clients even though clients are removed in the loop process.
Iterator<Messenger> ite = mClients.iterator();
while(ite.hasNext()){
try {
Message sendMsg = Message.obtain(null, CommonValue.MSG_SET_VALUE, null);
Bundle data = new Bundle();
data.putString("key", sendValue);
sendMsg.setData(data);
Messenger next = ite.next();
next.send(sendMsg);
} catch (RemoteException e) {
// If client does not exits, remove it from a list.
ite.remove();
}
}
}
@Override
public IBinder onBind(Intent intent) {
// *** POINT 4 *** Verify that the in-house signature permission is defined by an in-house application.
if (!SigPerm.test(this, MY_PERMISSION, myCertHash(this))) {
Toast.makeText(this, "In-house defined signature permission is not defined by in-house application.", Toa
st.LENGTH_LONG).show();
return null;
}
// *** POINT 5 *** Handle the received intent carefully and securely,
// even though the intent was sent from an in-house application.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
String param = intent.getStringExtra("PARAM");
Toast.makeText(this, String.format("Received parameter "%s".", param), Toast.LENGTH_LONG).show();
return mMessenger.getBinder();
}
@Override
public void onCreate() {
Toast.makeText(this, "Service - onCreate()", Toast.LENGTH_SHORT).show();
}
@Override
199
SigPerm.java
package org.jssec.android.shared;
import
import
import
import
android.content.Context;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
android.content.pm.PermissionInfo;
PkgCert.java
package org.jssec.android.shared;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import
import
import
import
import
android.content.Context;
android.content.pm.PackageInfo;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
android.content.pm.Signature;
200
201
*** Point 7 *** When exporting an APK from Eclipse, sign the APK with the same developer key as the
requesting application.
Figure 4.4-2
202
Next is the sample code of Activity which uses In house only Service.
Points (Using a Service):
8.
9.
10. Verify that the destination application is signed with the in-house certificate.
11. Sensitive information can be sent since the destination application is in-house.
12. Use the explicit intent to call an in-house service.
13. Handle the received result data carefully and securely, even though the data came from an
in-house application.
14. When exporting an APK from Eclipse, sign the APK with the same developer key as the
destination application.
AndroidManifest.xml
InhouseMessengerUserActivity.java
package org.jssec.android.service.inhouseservice.messengeruser;
import
import
import
import
org.jssec.android.service.inhouseservice.messengeruser.R;
org.jssec.android.shared.PkgCert;
org.jssec.android.shared.SigPerm;
org.jssec.android.shared.Utils;
import
import
import
import
import
android.app.Activity;
android.content.ComponentName;
android.content.Context;
android.content.Intent;
android.content.ServiceConnection;
203
import android.os.Bundle;
import
import
import
import
import
import
import
android.os.Handler;
android.os.IBinder;
android.os.Message;
android.os.Messenger;
android.os.RemoteException;
android.view.View;
android.widget.Toast;
204
}
}
}
// Connection used to connect with service. This is necessary when service is implemented with bindService().
private ServiceConnection mConnection = new ServiceConnection() {
// This is called when the connection with the service has been established.
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
mServiceMessenger = new Messenger(service);
Toast.makeText(mContext, "Connect to service", Toast.LENGTH_SHORT).show();
try {
// Send own messenger to service
Message msg = Message.obtain(null, CommonValue.MSG_REGISTER_CLIENT);
msg.replyTo = mActivityMessenger;
mServiceMessenger.send(msg);
} catch (RemoteException e) {
// Service stopped abnormally
}
}
// This is called when the service stopped abnormally and connection is disconnected.
@Override
public void onServiceDisconnected(ComponentName className) {
mServiceMessenger = null;
Toast.makeText(mContext, "Disconnected from service", Toast.LENGTH_SHORT).show();
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.inhouseservice_activity);
mContext = this;
}
// --- StartService control --public void onStartServiceClick(View v) {
// Start bindService
doBindService();
}
public void onGetInfoClick(View v) {
getServiceinfo();
}
public void onStopServiceClick(View v) {
doUnbindService();
}
@Override
protected void onDestroy() {
super.onDestroy();
doUnbindService();
}
205
/**
* Connect to service
*/
void doBindService() {
if (!mIsBound){
// *** POINT 9 *** Verify that the in-house signature permission is defined by an in-house application.
if (!SigPerm.test(this, MY_PERMISSION, myCertHash(this))) {
Toast.makeText(this, "In-house defined signature permission is not defined by in-house application.",
Toast.LENGTH_LONG).show();
return;
}
// *** POINT 10 *** Verify that the destination application is signed with the in-house certificate.
if (!PkgCert.test(this, TARGET_PACKAGE, myCertHash(this))) {
Toast.makeText(this, "Destination(Requested) service application is not in-house application.", Toast
.LENGTH_LONG).show();
return;
}
Intent intent = new Intent();
// *** POINT 11 *** Sensitive information can be sent since the destination application is in-house one.
intent.putExtra("PARAM", "Sensitive information");
// *** POINT 12 *** Use the explicit intent to call an in-house service.
intent.setClassName(TARGET_PACKAGE, TARGET_CLASS);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
}
/**
* Disconnect service
*/
void doUnbindService() {
if (mIsBound) {
unbindService(mConnection);
mIsBound = false;
}
}
/**
* Get information from service
*/
void getServiceinfo() {
if (mServiceMessenger != null) {
try {
// Request sending information
Message msg = Message.obtain(null, CommonValue.MSG_SET_VALUE);
mServiceMessenger.send(msg);
} catch (RemoteException e) {
// Service stopped abnormally
}
}
}
}
206
SigPerm.java
package org.jssec.android.shared;
import
import
import
import
android.content.Context;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
android.content.pm.PermissionInfo;
PkgCert.java
package org.jssec.android.shared;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import
import
import
import
import
android.content.Context;
android.content.pm.PackageInfo;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
android.content.pm.Signature;
207
208
*** Point14 *** When exporting an APK from Eclipse, sign the APK with the same developer key as the
destination application.
Figure 4.4-3
209
2.
3.
4.
5.
(Required)
(Required)
Application
(Required)
Use the In-house Defined Signature Permission after Verifying If it's Defined by an In-house
Do Not Determine Whether the Service Provides its Functions, in onCreate
(Required)
Destination Application
(Required)
Verify the Destination Service If Linking with the Other Company's Application
(Required)
Protection
(Required)
When Returning a Result Information, Pay Attention the Result Information Leakage from the
6.
8.
When Providing an Asset Secondarily, the Asset should be protected with the Same Level
7.
9.
(Required)
(Recommended)
(Required)
Service that is used only in an application (or in same UID) must be set as Private. It avoids the
application from receiving Intents from other applications unexpectedly and eventually prevents
from damages such as application functions are used or application behavior becomes abnormal.
All you have to do in implementation is set exported attribute false when defining Service in
AndroidManifest.xml.
AndroidManifest.xml
In addition, this is a rare case, but do not set Intent Filter when service is used only within the
application. The reason is that, due to the characteristics of Intent Filter, public service in other
application may be called unexpectedly though you intend to call Private Service within the
application.
AndroidManifest.xml(Not recommended)
210
http://www.jssec.org/dl/android_securecoding_en.pdf
See "4.4.3.1 Combination of Exported Attribute and Intent-filter Setting (In the Case of Service)."
4.4.2.2. Handle the Received Data Carefully and Securely
(Required)
Same like Activity, In case of Service, when processing a received Intent data, the first thing you
should do is input validation. Also in Service user side, it's necessary to verify the safety of result
information from Service. Please refer to "4.1.2.5 Handling the Received Intent Carefully and Securely
(Required)" and "4.1.2.9 Handle the Returned Data from a Requested Activity Carefully and
Securely
(Required)."
In Service, you should also implement calling method and exchanging data by Message carefully.
Please refer to "3.2 Handling Input Data Carefully and Securely"
4.4.2.3. Use the In-house Defined Signature Permission after Verifying If it's Defined by an In-house
Application
(Required)
Make sure to protect your in-house Services by defining in-house signature permission when
creating the Service. Since defining a permission in the AndroidManifest.xml file or declaring a
permission request does not provide adequate security, please be sure to refer to "5.2.1.2 How to
Communicate Between In-house Applications with In-house-defined Signature Permission."
4.4.2.4. Do Not Determine Whether the Service Provides its Functions, in onCreate
(Required)
verification should not be included in onCreate, because when receiving new request during Service
is running, process of onCreate is not executed. So, when implementing Service which is started by
judgment should be executed by onHandleIntent.) It's also same in the case when implementing
Service which is started by bindService, judgment should be executed by onBind.
4.4.2.5. When Returning a Result Information, Pay Attention the Result Information Leakage from the
Destination Application
(Required)
Depends on types of Service, the reliability of result information destination application (callback
receiver side/ Message destination) are different. Need to consider seriously about the information
See, Activity "4.1.2.7 When Returning a Result, Pay Attention to the Possibility of Information
Leakage of that Result from the Destination Application
(Required)
When using a Service by implicit Intents, in case the definition of Intent Filter is same, Intent is sent to
4.4 Creating/Using Services
211
the Service which was installed earlier. If Malware with the same Intent Filter defined intentionally was
installed earlier, Intent is sent to Malware and information leakage occurs. On the other hand, when
using a Service by explicit Intents, only the intended Service will receive the Intent so this is much
safer.
There are some other points which should be considered, please refer to "4.1.2.8 Use the explicit
Intents if the destination Activity is predetermined.
(Required)."
4.4.2.7. Verify the Destination Service If Linking with the Other Company's Application (Required)
Be sure to sure a whitelist when linking with another company's application. You can do this by
saving a copy of the company's certificate hash inside your application and checking it with the
certificate hash of the destination application. This will prevent a malicious application from being
able to spoof Intents. Please refer to sample code section "4.4.1.3 Creating/Using Partner Service" for
4.4.2.8. When Providing an Asset Secondarily, the Asset should be protected with the Same Level
Protection
(Required)
application secondhand, you need to make sure that it has the same required permissions needed to
access the asset. In the Android OS permission security model, only an application that has been
granted proper permissions can directly access a protected asset. However, there is a loophole
because an application with permissions to an asset can act as a proxy and allow access to an
(Recommended)
Not sending sensitive data in the first place is the only perfect solution to prevent information
leakage therefore you should limit the amount of sensitive information being sent as much as
possible. When it is necessary to send sensitive information, the best practice is to only send to a
trusted Service and to make sure the information cannot be leaked through LogCat
212
http://www.jssec.org/dl/android_securecoding_en.pdf
Public Services, Partner Services, and In-house Services. The various combinations of permitted
settings for each type of exported attribute defined in the AndroidManifest.xml file and the
intent-filter elements are defined in the table below. Please verify the compatibility of the exported
attribute and intent-filter element with the Service you are trying to create.
Table 4.4-3
Value of exported attribute
True
false
Not specified
Public, Partner
Public, Partner
Intent
Public, Partner,
Private
Private
Filter
Not
Defined
In-house
The reason why an undefined intent filter and an exported attribute of false should not be used is
that there is a loophole in Android's behavior, and because of how Intent filters work, other
application's Services can be called unexpectedly.
Concretely, Android behaves as per below, so it's necessary to consider carefully when application
designing.
When multiple Services define the same content of intent-filter, the definition of Service within
application installed earlier is prioritized.
In case explicit Intent is used, prioritized Service is automatically selected and called by OS.
The system that unexpected call is occurred due to Android's behavior is described in the three
figures below. Figure 4.4-4 is an example of normal behavior that Private Service (application A) can
be called by implicit Intent only from the same application. Because only application A defines
Intent-filter (action="X" in the Figure), it behaves normally. This is the normal behavior.
213
http://www.jssec.org/dl/android_securecoding_en.pdf
Application A
Call a service with
the implicit intent
Intent(X)
Application C
Call the service with
the implicit intent
Intent(X)
Figure 4.4-4
Figure 4.4-5 and Figure 4.4-6 below show a scenario in which the same Intent filter (action="X") is
Figure 4.4-5 shows the scenario that applications are installed in the order, application A ->
application B. In this case, when application C sends implicit Intent, calling Private Service (A-1) fails.
On the other hand, since application A can successfully call Private Service within the application by
implicit Intent as expected, there won't be any problems in terms of security (counter-measure for
Malware).
214
http://www.jssec.org/dl/android_securecoding_en.pdf
Application A
Call a service with
the implicit intent
Intent(X)
Application C
Call the service with
the implicit intent
Intent(X)
Application B
Public Service B-1
exported=true
action=X
Android device
Figure 4.4-5
Figure
4.4-6
shows
the
scenario
that
applications
are
installed
in
the
order,
possible that sensitive information can be sent from applicationA to applicationB. If applicationB is
Malware, it will lead the leakage of sensitive information.
215
http://www.jssec.org/dl/android_securecoding_en.pdf
Application C
Call the service with
the implicit intent
Application B
Public Service B-1
exported=true
action=X
Intent(X)
Application A
Call a service with
the implicit intent
Intent(X)
Private Service A-1
exported=false
action=X
Android device
As shown above, using Intent filters to send implicit Intents to Private Service may result in
unexpected behavior so it is best to avoid this setting.
4.4.3.2. How to Implement Service
Because methods for Service implementation are various and should be selected with consideration
of security type which is categorized by sample code, each characteristics are briefly explained. It's
divided roughly into the case using startService and the case using bindService. And it's also possible
to create Service which can be used in both startService and bindService. Following items should be
investigated to determine the implementation method of Service.
Whether to exchange data during running or not (Mutual sending /receiving data)
Whether to control Service or not (Launch or complete)
Table 4.4-3 shows category of implementation methods and feasibility of each item.
"NG" stands for impossible case or case that another frame work which is different from the provided
function is required.
216
Parallel
process
NG
IntentService
OK
NG
NG
OK
NG
OK
NG
NG
Messenger bind
OK
OK
OK
OK
OK
OK
OK
type
type
OK
OK
OK
NG
NG
startService type
This is the most basic Service. This inherits Service class, and executes processes by
onStartCommand.
In user side, specify Service by Intent, and call by startService. Because data such as results
cannot be returned to source of Intent directly, it should be achieved in combination with another
method such as Broadcast. Please refer to "4.4.1.1 Creating/Using Private Service" for the
concrete example.
Checking in terms of security should be done by onStartCommand, but it cannot be used for
partner only Service since the package name of the source cannot be obtained.
IntentService type
IntentService is the class which was created by inheriting Service. Calling method is same as
startService type. Following are characteristics compared with standard service (startService
type.)
Call is immediately returned because process is executed by another thread, and process
towards Intents is sequentially executed by Queuing system. Each Intent is not processed in
parallel, but it is also selectable depending on the product's requirement, as an option to simplify
implementation. Since data such as results cannot be returned to source of Intent, it should be
achieved in combination with
"4.4.1.2
Checking in terms of security should be done by onHandleIntent, but it cannot be used for
partner only Service since the package name of the source cannot be obtained.
217
This is a method to implement local Service which works only within the process same as an
application. Define the class which was derived from Binder class, and prepare to provide the
feature (method) which was implemented in Service to caller side.
From user side, specify Service by Intent and call Service by using bindService. This is the most
simple implementation method among all methods of binding Service, but it has limited usages
since it cannot be launched by another process and also Service cannot be disclosed. See project
From the security point of view, only private Service can be implemented.
Messenger bind type
This is the method to achieve the linking with Service by using Messenger system.
Since Messenger can be given as a Message destination from Service user side, the mutual data
exchanging can be achieved comparatively easily. In addition, since processes are to be queued,
it has a characteristic that behaves "thread-safe"ly. Parallel process for each process is not
possible, but it is also selectable as an option to simplify the implementation depending on the
product's requirement. Regarding user side, specify Service by Intent, and call Service by using
bindService. See "4.4.1.4 Creating/Using In-house Service" for the concrete implementation
example.
Security check in onBind or by Message Handler is necessary, however, it cannot be used for
This is a method to achieve linking with Service by using AIDL system. Define interface by AIDL,
and provide features that Service has as a method. In addition, call back can be also achieved by
implementing interface defined by AIDL in user side, Multi-thread calling is possible, but it's
necessary to implement explicitly in Service side for exclusive process.
User side can call Service, by specifying Intent and using bindService. Please refer to "4.4.1.3
Security must be checked in onBind for In-house only Service and by each method of interface
This can be used for all security types of Service which are described in this Guidebook.
218
Main points are appropriate setting of access right to database file, and counter-measures for SQL
injection. Database which permits reading/writing database file from outside directly (sharing among
multiple applications) is not supposed here, but suppose the usage in backend of Content Provider
below in case of handling not so much sensitive information, though handling a certain level of
sensitive information is supposed here.
4.5.1. Sample Code
4.5.1.1. Creating/Operating Database
When handling database in Android application, appropriate arrangements of database files and
access right setting (Setting for denying other application's access) can be achieved by using
SQLiteOpenHelper 4. Here is an example of easy application that creates database when it's launched,
and executes searching /adding/changing/deleting data through UI. Sample code is what
counter-measure for SQL injection is done, to avoid from incorrect SQL being executed against the
As regarding file storing, the absolute file path can be specified as the 2nd parameter (name) of
SQLiteOpenHelper constructor. Therefore, need attention that the stored files can be read and written
219
Figure 4.5-1
Points:
1.
2.
3.
SampleDbOpenHelper.java
package org.jssec.android.sqlite;
import org.jssec.android.sqlite.R;
import
import
import
import
import
import
android.content.Context;
android.database.SQLException;
android.database.sqlite.SQLiteDatabase;
android.database.sqlite.SQLiteOpenHelper;
android.util.Log;
android.widget.Toast;
220
221
+ ");";
public SampleDbOpenHelper(Context context) {
super(context, CommonData.DBFILE_NAME, null, CommonData.DB_VERSION);
mContext = context;
}
@Override
public void onCreate(SQLiteDatabase db) {
try {
db.execSQL(CREATE_TABLE_COMMANDS); //Execute DB construction command
} catch (SQLException e) {
//In case failed to construct database, output to log
Log.e(this.getClass().toString(), mContext.getString(R.string.DATABASE_CREATE_ERROR_MESSAGE));
}
}
@Override
public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) {
// It's to be executed when database version up. Write processes like data transition.
}
}
org.jssec.android.sqlite.CommonData;
org.jssec.android.sqlite.DataValidator;
org.jssec.android.sqlite.MainActivity;
org.jssec.android.sqlite.R;
import
import
import
import
import
android.database.Cursor;
android.database.SQLException;
android.database.sqlite.SQLiteDatabase;
android.os.AsyncTask;
android.util.Log;
222
//*** POINT 3 *** Validate the input value according the application requirements.
if (!DataValidator.validateData(idno, name, info))
{
return null;
}
//When all parameters are null, execute all search
if ((idno == null || idno.length() == 0) &&
(name == null || name.length() == 0) &&
(info == null || info.length() == 0) ) {
try {
cur = mSampleDB.query(CommonData.TABLE_NAME, cols, null, null, null, null, null);
} catch (SQLException e) {
Log.e(DataSearchTask.class.toString(), mActivity.getString(R.string.SEARCHING_ERROR_MESSAGE));
return null;
}
return cur;
}
//When No is specified, execute searching by No
if (idno != null && idno.length() > 0) {
String selectionArgs[] = {idno};
try {
//*** POINT 2 *** Use place holder.
cur = mSampleDB.query(CommonData.TABLE_NAME, cols, "idno = ?", selectionArgs, null, null, null);
} catch (SQLException e) {
Log.e(DataSearchTask.class.toString(), mActivity.getString(R.string.SEARCHING_ERROR_MESSAGE));
return null;
}
return cur;
}
//When Name is specified, execute perfect match search by Name
if (name != null && name.length() > 0) {
String selectionArgs[] = {name};
try {
//*** POINT 2 *** Use place holder.
cur = mSampleDB.query(CommonData.TABLE_NAME, cols, "name = ?", selectionArgs, null, null, null);
} catch (SQLException e) {
Log.e(DataSearchTask.class.toString(), mActivity.getString(R.string.SEARCHING_ERROR_MESSAGE));
return null;
}
return cur;
}
//Other than above, execute partly match searching with
String argString = info.replaceAll("@", "@@"); //Escape
argString = argString.replaceAll("%", "@%"); //Escape %
argString = argString.replaceAll("_", "@_"); //Escape _
String selectionArgs[] = {argString};
try {
//*** POINT 2 *** Use place holder.
cur = mSampleDB.query(CommonData.TABLE_NAME, cols, "info LIKE '%' || ? || '%' ESCAPE '@'", selectionArgs,
null, null, null);
} catch (SQLException e) {
Log.e(DataSearchTask.class.toString(), mActivity.getString(R.string.SEARCHING_ERROR_MESSAGE));
return null;
}
223
return cur;
}
@Override
protected void onPostExecute(Cursor resultCur) {
mActivity.updateCursor(resultCur);
}
}
DataValidator.java
package org.jssec.android.sqlite;
public class DataValidator {
//Validate the Input value
//validate numeric characters
public static boolean validateNo(String idno) {
//null and blank are OK
if (idno == null || idno.length() == 0) {
return true;
}
//Validate that it's numeric character.
try {
if (!idno.matches("[1-9][0-9]*")) {
//Error if it's not numeric value
return false;
}
} catch (NullPointerException e) {
//Detected an error
return false;
}
return true;
}
// Validate the length of a character string
public static boolean validateLength(String str, int max_length) {
//null and blank are OK
if (str == null || str.length() == 0) {
return true;
}
//Validate the length of a character string is less than MAX
try {
if (str.length() > max_length) {
//When it's longer than MAX, error
return false;
}
} catch (NullPointerException e) {
//Bug
return false;
}
return true;
}
// Validate the Input value
public static boolean validateData(String idno, String name, String info) {
224
if (!validateNo(idno)) {
return false;
}
if (!validateLength(name, CommonData.TEXT_DATA_LENGTH_MAX)) {
return false;
}
if (!validateLength(info, CommonData.TEXT_DATA_LENGTH_MAX)) {
return false;
}
return true;
}
}
225
2.
3.
(Required)
Use Content Provider for Access Control When Sharing DB Data with Other Application
(Required)
Place Holder Must Be Used in the Case Handling Variable Parameter during DB Operation.
(Required)
(Required)
Considering the protection of DB file data, DB file location and access right setting is the very
For example, even if file access right is set correctly, a DB file can be accessed from anybody in case
that it is arranged in a location which access right cannot be set, e.g. SD card. And in case that it's
arranged in application directory, if the access right is not correctly set, it will eventually allow the
unexpected access. Following are some points to be met regarding the correct allocation and access
About location and access right setting, considering in terms of protecting DB file (data), it's
necessary to execute 2 points as per below.
1.
2.
Location
Access right
Set to MODE_PRIVATE (=it can be accessed only by the application which creates file) mode.
By executing following 2 points, DB file which cannot be accessed by other applications can be
Use SQLiteOpenHelper
Use Context#openOrCreateDatabase
this method, DB files which can be read out from other applications are created, in some Android
smartphone devices. So it is recommended to avoid this method, and using other methods. Each
Both methods provide the path under (package) directory which is able to be read and written only by
the specified application.
226
Using SQLiteOpenHelper
When using SQLiteOpenHelper, developers don't need to be worried about many things. Create a
class derived from SQLiteOpenHelper, and specify DB name (which is used for file name) 6 to
constructer's parameter, then DB file which meets above security requirements, are to be created
automatically.
Refer to specific usage method for "4.5.1.1 Creating/Operating Database" for how to use.
Using Context#openOrCreateDatabase
Regarding file arrangement, specifying DB name (which is to be used to file name) can be done as
same as SQLiteOpenHelper, a file is to be created automatically, in the file path which meets the
above mentioned security requirements. However, full path can be also specified, so it's
necessary to pay attention that when specifying SD card, even though specifying MODE_PRIVATE,
//Construct database
try {
//Create DB by setting MODE_PRIVATE
db = Context.openOrCreateDatabase("Sample.db",
MODE_PRIVATE, null);
} catch (SQLException e) {
//In case failed to construct DB, log output
Log.e(this.getClass().toString(), getString(R.string.DATABASE_OPEN_ERROR_MESSAGE));
return;
}
//Ommit other initial process
}
FYI, there are following 3 types of access right setting including MODE_PRIVATE.
calculation. When using other than MODE_PRIVATE, need to consider carefully along with the
application requirements.
MODE_PRIVATE
(Undocumented in Android reference) Since the full file path can be specified as the database name
inSQLiteOpenHelper implementation, need attention that specifying the place (path) which does not
have access control feature (e.g. sdcards) unintentionally.
4.5 Using SQLite
227
MODE_WORLD_READABLE
MODE_WORLD_WRITABLE
Creator application can read and write, Others can only read in
Creator application can read and write, Others can only write in
4.5.2.2. Use Content Provider for Access Control When Sharing DB Data with Other Application
(Required)
The method to share DB data with other application is that create DB file as WORLD_READABLE,
WORLD_WRITABLE, to other applications to access directly. However, this method cannot limit
applications which access to DB or operations to DB, so data can be read-in or written by unexpected
party (application). As a result, it can be considered that some problems may occur in confidentiality
As mentioned above, when sharing DB data with other applications in Android, it's strongly
recommended to use Content Provider. By using Content Provider, there are some merits, not only
the merits from the security point of view which is the access control on DB can be achieved, but also
merits from the designing point of view which is DB scheme structure can be hidden into Content
Provider.
4.5.2.3. Place Holder Must Be Used in the Case Handling Variable Parameter during DB Operation.
(Required)
In the sense that preventing from SQL injection, when incorporating the arbitrary input value to SQL
statement, placeholder should be used. There are 2 methods as per below to execute SQL using
placeholder.
1.
2.
When calling execSQL(), insert(), update(), delete(), query(), rawQuery() and replace() etc in
SQLiteDatabese class, use SQL statement which has placeholder.
is a limitation that "only the top 1 element can be obtained as a result of SELECT command," so
In either method, the data content which is given to placeholder is better to be checked in advance
according the application requirements. Following is the further explanation for each method.
When Using SQLiteDatabase#compileStatement():
This is so called prepared statement; data is given to placeholder in the following steps.
228
1.
Get
the
SQL
statement
which
includes
2.
Set the created as SQLiteStatement objects to placeholder by using the method like
SQLiteDatabase#compileStatement(), as SQLiteStatement.
placeholder
by
using
3.
This is a type that SQL statement to be executed as object is created in advance, and parameters
are allocated to it. The process to execute is fixed, so there's no room for SQL injection to occur.
In the Case Using Method for Each Process which SQLiteDatabase provides:
There are 2 types of DB operation methods that SQLiteDatabase provides. One is what SQL
statement is used, and another is what SQL statement is not used. Methods that SQL statement is
used are SQLiteDatabase# execSQL()/rawQuery() etc, and it's executed in the following steps.
4.5 Using SQLite
229
1.
2.
3.
Send SQL statement and data as parameter, and execute a method for process.
2.
Send ContentValues as parameter, and execute a method for each process (In the following
example, SQLiteDatabase#insert() )
In this example, SQL command is not directly written, for instead, a method for inserting which
SQLiteDatabase provides, is used. SQL command is not directly used, so there's no room for SQL
230
holder, it will work as a wild card unless it is processed properly, so it's necessary to implement
escape process in advance according the necessity. It is the case which escape process is necessary
The actual escape process is executed by using ESCAPE clause as per below sample code.
Example of ESCAPE process in case of using LIKE
231
mProgressDialog.dismiss();
mActivity.updateCursor(resultCur);
}
}
4.5.3.2. Use External Input to SQL Command in which Place Holder Cannot Be Used
When executing SQL statement which process targets are DB objects like table creation/deletion etc,
placeholder cannot be used for the value of table name. Basically, DB should not be designed using
arbitrary character string which was input from outside in case that placeholder cannot be used for
the value.
When placeholder cannot be used due to the restriction of specifications or features, whether the
Input value is dangerous or not, should be verified before execution, and it's necessary to implement
necessary processes.
Basically,
1.
When using as character string parameter, escape or quote process for character should be
2.
When using as numeric value parameter, verify that characters other than numeric value are not
3.
made.
included.
When using as identifier or command, verify whether characters which cannot be used are not
should be executed.
Reference: http://www.ipa.go.jp/security/vuln/documents/website_security_sql.pdf (Japanese)
4.5.3.3. Take a Countermeasure that Database Is Not Overwritten Unexpectedly
In case getting instance of DB by SQLiteOpenHelper#getReadableDatabase, getWritableDatabase,
SQLiteDatabase#openOrCreateDatabase,
etc.
It
means
that
implementation. Basically, it can be supported by the application's spec and range of implementation,
but when implementing the function which requires only read in function like application's searching
function etc, opening database by read-only, it may lead to simplify designing or inspection and
furthermore, lead to enhance application quality, so it's recommended depends on the situation.
getReableDatabase() returns the same object which can be got by getWritableDatabase. This spec is,
in case writable object cannot be generated due to disc full etc, it will return Read- only object.
(getWritableDatabase() will be execution error under the situation like disc full etc.)
232
Reference: http://developer.android.com/reference/android/database/sqlite/SQLiteOpenHelper.ht
ml#getReadableDatabase()
4.5.3.4. Verify the Validity of Input/Output Data of DB, According to Application's Requirement
SQLite is the database which is tolerant types, and it can store character type data into columns which
is declared as Integer in DB. Regarding data in database, all data including numeric value type is
stored in DB as character data of plain text. So searching of character string type, can be executed to
Integer type column. (LIKE '%123%' etc.) In addition, the limitation for the value in SQLite (validity
verification) is untrustful since data which is longer than limitation can be input in some case, e.g.
VARCHAR(100).
So, applications which use SQLite, need to be very careful about this characteristics of DB, and it's
necessary take actions according to application requirements, not to store unexpected data to DB
or not to get unexpected data. Countermeasures are as per below 2 points.
1.
When storing data in database, verify that type and length are matched.
2.
When getting the value from database, verify whether data is beyond the supposed type and
length, or not.
Following is an example of the code which verifies that the Input value is more than 1.
Verify that the Input value is more than 1 (Extract from MainActivity.java)
public class MainActivity extends Activity {
... Abbreviation ...
//Process for adding
private void addUserData(String idno, String name, String info) {
//Check for No
if (!validateNo(idno, CommonData.REQUEST_NEW)) {
return;
}
//Inserting data process
DataInsertTask task = new DataInsertTask(mSampleDbyhis);
task.execute(idno, name, info);
}
... Abbreviation ...
233
All data including numeric value type are stored into DB file as character data of plain text.
When executing data deletion to DB, data itself is not deleted form DB file. (Only deletion mark is
added.)
When updating data, data before updating has not been deleted, and still remains there in DB
file.
So, the information which "must have" been deleted may still remain in DB file. Even in this case, take
counter-measures according this Guidebook, and when Android security function is enabled,
data/file may not be directly accessed by the third party including other applications. However,
considering the case that files are picked out by passing through Android's protection system like
root privilege is taken, in case the data which gives huge influence on business is stored, data
protection which doesn't depend on Android protection system, should be considered.
As above reasons, the important data which is necessary to be protected even when device's root
privilege is taken, should not be stored in DB of SQLite, as it is. In case need to store the important
http://www.jssec.org/dl/android_securecoding_en.pdf
When encryption is necessary, there are so many issues that are beyond the range of this Guidebook,
like handling the key which is used for encryption or code obfuscation, so as of now it's
recommended to consult the specialist when developing an application which handles data that has
Please refer to "4.5.3.6 [Reference] Encrypt SQLite Database (SQLCipher for Android," library which
encrypts database is introduced here.
It's open sourced (BSD license), and maintained/managed by Zetetic LLC. In a world of mobile,
SQLCipher for Android project is aiming to support the standard integrated encryption for SQLite
database in Android environment. By creating the standard SQLite's API for SQLCipher, developers
can use the encrypted database with the same coding as per usual.
Reference: https://guardianproject.info/code/sqlcipher/
How to Use
3.
Locate
sqlcipher.jar,
libdatabase_sqlcipher.so,
libsqlcipher_android.so
and
Regarding all source files, change all android.database.sqlite.* which is specified by import,
to info.guardianproject.database.sqlite.*. In addition, android.database.Cursor can be used
as it is.
SQLiteDatabase.loadLibs(this);
// First, Initialize library by using context.
SQLiteOpenHelper.getWritableDatabase(passwoed): // Parameter is password(Suppose that it's string type and It's got
in a secure way.)
SQLCipher for Android was version 1.1.0 at the time of writing, and now version 2.0.0 is under
developing, and RC4 is disclosed now.
it's necessary to be verified later, but currently still there's a room to consider as encryption
The following files which are included as SDK, are necessary, to use SQLCipher.
235
http://www.jssec.org/dl/android_securecoding_en.pdf
assets/icudt46l.zip
2,252KB
It's necessary when icudt46l.dat doesn't exist below /system/usr/icu/ and its earlier version.
libs/armeabi/libdatabase_sqlcipher.so
libs/armeabi/libsqlcipher_android.so
libs/armeabi/libstlport_shared.so
Native
Library.
It's
read
SQLiteDatabase#loadLibs()).
libs/commons-codec.jar
libs/guava-r09.jar
libs/sqlcipher.jar
out
44KB
1,117KB
when
555KB
SQLCipher's
initial
load
(When
calling
46KB
1,116KB
102KB
Java library which calls Native library. sqlcipher.jar is main. Others are referred from
sqlcipher.jar.
236
and temporary save (cache), and it should be private in principle. Exchanging information between
applications should not be direct access to files, but it should be exchanged by inter-application
linkage system, like Content Provider or Service. By using this, inter-application access control can
be achieved.
Since enough access control cannot be performed on external memory device like SD card etc, so it
should be limited to use only when it's necessary by all means in terms of function, like when
handling huge size files or transferring information to another location (PC etc.). Basically, files that
include sensitive information should not be saved in external memory device. In case sensitive
information needs to be saved in a file of external device at any rate, counter-measures like
encryption are necessary, but it's not referred here.
of files based on the file storage location or access permission to other application. Sample code for
each file category is shown below and explanation for each of them are also added there.
Table 4.6-1 File category and comparison from security point of view
File category
Access permission
Storage
Overview
Private file
NA
In
Read out
public file
Read write
public file
External
memory
device
(Read write
public)
to other application
Read out
Read out
Write in
Read out
Write in
location
application
directory
In
application
In
directory
directory
application
External
view.
memory
SD card
device like
No access control
handled.
237
private as much as possible, and when exchanging the necessary information with other applications,
2.
The access privilege of file must be set private mode in order not to be used by other
3.
applications.
4.
Regarding the information to be stored in files, handle file data carefully and securely.
PrivateFileActivity.java
package org.jssec.android.file.privatefile;
import
import
import
import
import
java.io.File;
java.io.FileInputStream;
java.io.FileNotFoundException;
java.io.FileOutputStream;
java.io.IOException;
import org.jssec.android.file.privatefile.R;
import
import
import
import
android.app.Activity;
android.os.Bundle;
android.view.View;
android.widget.TextView;
238
239
file.delete();
mFileView.setText(R.string.file_view);
}
}
PrivateUserActivity.java
package org.jssec.android.file.privatefile;
import
import
import
import
java.io.FileInputStream;
java.io.FileNotFoundException;
java.io.FileOutputStream;
java.io.IOException;
import org.jssec.android.file.privatefile.R;
import
import
import
import
import
android.app.Activity;
android.content.Intent;
android.os.Bundle;
android.view.View;
android.widget.TextView;
240
try {
fis = openFileInput(FILE_NAME);
byte[] data = new byte[(int) fis.getChannel().size()];
fis.read(data);
// *** POINT 4 *** Regarding the information to be stored in files, handle file data carefully and securel
y.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
String str = new String(data);
mFileView.setText(str);
} catch (FileNotFoundException e) {
mFileView.setText(R.string.file_view);
} catch (IOException e) {
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
}
}
}
}
/**
* Rewrite file process
*
* @param view
*/
public void onWriteFileClick(View view) {
FileOutputStream fos = null;
try {
// *** POINT 1 *** Files must be created in application directory.
// *** POINT 2 *** The access privilege of file must be set private mode in order not to be used by other
applications.
fos = openFileOutput(FILE_NAME, MODE_APPEND);
// *** POINT 3 *** Sensitive information can be stored.
// *** POINT 4 *** Regarding the information to be stored in files, handle file data carefully and securel
y.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
fos.write(new String("Sensitive information (User Activity)n").getBytes());
} catch (FileNotFoundException e) {
mFileView.setText(R.string.file_view);
} catch (IOException e) {
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
}
}
}
callFileActivity();
}
}
241
242
you implement by following the below points, it's also comparatively safe file usage method.
Points:
1.
2.
The access privilege of file must be set to read only to other applications.
4.
Regarding the information to be stored in files, handle file data carefully and securely.
3.
PublicFileActivity.java
package org.jssec.android.file.publicfile.readonly;
import
import
import
import
import
java.io.File;
java.io.FileInputStream;
java.io.FileNotFoundException;
java.io.FileOutputStream;
java.io.IOException;
import org.jssec.android.file.publicfile.readonly.R;
import
import
import
import
android.app.Activity;
android.os.Bundle;
android.view.View;
android.widget.TextView;
243
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
fos.write(new String("Not sensitive information (Public File Activity)n")
.getBytes());
} catch (FileNotFoundException e) {
mFileView.setText(R.string.file_view);
} catch (IOException e) {
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
}
}
}
finish();
}
/**
* Read file process
*
* @param view
*/
public void onReadFileClick(View view) {
FileInputStream fis = null;
try {
fis = openFileInput(FILE_NAME);
byte[] data = new byte[(int) fis.getChannel().size()];
fis.read(data);
String str = new String(data);
mFileView.setText(str);
} catch (FileNotFoundException e) {
mFileView.setText(R.string.file_view);
} catch (IOException e) {
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
}
}
}
}
/**
* Delete file process
*
* @param view
*/
public void onDeleteFileClick(View view) {
File file = new File(this.getFilesDir() + "/" + FILE_NAME);
file.delete();
mFileView.setText(R.string.file_view);
}
244
PublicUserActivity.java
package org.jssec.android.file.publicuser.readonly;
import
import
import
import
import
java.io.File;
java.io.FileInputStream;
java.io.FileNotFoundException;
java.io.FileOutputStream;
java.io.IOException;
import org.jssec.android.file.publicuser.readonly.R;
import
import
import
import
import
import
import
import
android.app.Activity;
android.content.ActivityNotFoundException;
android.content.Context;
android.content.Intent;
android.content.pm.PackageManager.NameNotFoundException;
android.os.Bundle;
android.view.View;
android.widget.TextView;
245
/**
* Read file process
*
* @param view
*/
public void onReadFileClick(View view) {
FileInputStream fis = null;
try {
File file = new File(getFilesPath(FILE_NAME));
fis = new FileInputStream(file);
byte[] data = new byte[(int) fis.getChannel().size()];
fis.read(data);
// *** POINT 4 *** Regarding the information to be stored in files, handle file data carefully and securel
y.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
String str = new String(data);
mFileView.setText(str);
} catch (FileNotFoundException e) {
} catch (IOException e) {
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
}
}
}
}
/**
* Rewrite file process
*
* @param view
*/
public void onWriteFileClick(View view) {
FileOutputStream fos = null;
boolean exception = false;
try {
File file = new File(getFilesPath(FILE_NAME));
// Fail to write in. FileNotFoundException occurs.
fos = new FileOutputStream(file, true);
fos.write(new String("Not sensitive information (Public User Activity)n")
.getBytes());
} catch (FileNotFoundException e) {
mFileView.setText(e.getMessage());
exception = true;
} catch (IOException e) {
mFileView.setText(e.getMessage());
exception = true;
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
exception = true;
246
}
}
}
if (exception == false)
callFileActivity();
}
private final String getFilesPath(String filename) {
String path = "";
try {
Context ctx = createPackageContext(TARGET_PACKAGE,
Context.CONTEXT_RESTRICTED);
File file = new File(ctx.getFilesDir(), filename);
path = file.getPath();
} catch (NameNotFoundException e) {
}
return path;
}
}
247
Unspecified large number of application can read and write, means that needless to say. Malware can
also read and write, so the credibility and safety of data will be never guaranteed. In addition, even in
case of not malicious intention, data format in file or timing to write in cannot be controlled. So this
type of file is almost not practical in terms of functionality.
As above, it's impossible to use read-write files safely from both security and application designing
points of view, so using read-write files should be avoided.
Point:
1.
248
Must not create files that be allowed to read/write access from other applications.
storing comparatively huge information (placing file which was downloaded from Web), or when
bring out the information to outside (backup etc.)
"External memory file (Read Write public)" has the equal characteristics with "Read Write public file" to
unspecified large number of applications. In addition, it has the equal characteristics with "Read Write
public file" to applications which declares to use android.permission.WRITE_EXTERNAL_STORAGE
Permission. So, the usage of "External memory file (Read Write public) file" should be minimized as
less as possible.
A Backup file is most probably created in an external memory device as Android application's
customary practice. However, as mentioned as above, files in an external memory have the risk that
is tampered/ deleted by other applications including malware. Hence, in applications which output
backup, some contrivances to minimize risks in terms of application spec or designing like
displaying a caution "Copy Backup files to the safety location like PC etc, a.s.a.p.", are necessary.
Points:
1.
2.
4.
3.
Regarding the information to be stored in files, handle file data carefully and securely.
AndroidManifest.xml
249
ExternalFileActivity.java
package org.jssec.android.file.externalfile;
import
import
import
import
import
java.io.File;
java.io.FileInputStream;
java.io.FileNotFoundException;
java.io.FileOutputStream;
java.io.IOException;
import org.jssec.android.file.externalfile.R;
import
import
import
import
android.app.Activity;
android.os.Bundle;
android.view.View;
android.widget.TextView;
250
}
finish();
}
/**
* Read file process
*
* @param view
*/
public void onReadFileClick(View view) {
FileInputStream fis = null;
try {
File file = new File(getExternalFilesDir(TARGET_TYPE), FILE_NAME);
fis = new FileInputStream(file);
byte[] data = new byte[(int) fis.getChannel().size()];
fis.read(data);
// *** POINT 3 *** Regarding the information to be stored in files, handle file data carefully and securel
y.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
String str = new String(data);
mFileView.setText(str);
} catch (FileNotFoundException e) {
mFileView.setText(R.string.file_view);
} catch (IOException e) {
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
}
}
}
}
/**
* Delete file process
*
* @param view
*/
public void onDeleteFileClick(View view) {
File file = new File(getExternalFilesDir(TARGET_TYPE), FILE_NAME);
file.delete();
mFileView.setText(R.string.file_view);
}
}
ExternalFileUser.java
package org.jssec.android.file.externaluser;
import java.io.File;
import java.io.FileInputStream;
251
import java.io.FileNotFoundException;
import java.io.IOException;
import org.jssec.android.file.externaluser.R;
import
import
import
import
import
import
import
import
import
import
android.app.Activity;
android.app.AlertDialog;
android.content.ActivityNotFoundException;
android.content.Context;
android.content.DialogInterface;
android.content.Intent;
android.content.pm.PackageManager.NameNotFoundException;
android.os.Bundle;
android.view.View;
android.widget.TextView;
252
try {
File file = new File(getFilesPath(FILE_NAME));
fis = new FileInputStream(file);
byte[] data = new byte[(int) fis.getChannel().size()];
fis.read(data);
// *** POINT 3 *** Regarding the information to be stored in files, handle file data carefully and securel
y.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
String str = new String(data);
mFileView.setText(str);
} catch (FileNotFoundException e) {
mFileView.setText(R.string.file_view);
} catch (IOException e) {
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
}
}
}
}
/**
* Rewrite file process
*
* @param view
*/
public void onWriteFileClick(View view) {
// *** POINT 4 *** Writing file by the requesting application should be prohibited as the specification.
// Application should be designed supposing malicious application may overwrite or delete file.
final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(
this);
alertDialogBuilder.setTitle("POINT 4");
alertDialogBuilder.setMessage("Do not write in calling appllication.");
alertDialogBuilder.setPositiveButton("OK",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
callFileActivity();
}
});
alertDialogBuilder.create().show();
}
private final String getFilesPath(String filename) {
String path = "";
try {
Context ctx = createPackageContext(TARGET_PACKAGE,
Context.CONTEXT_IGNORE_SECURITY);
253
254
(Required)
Using Files Stored in External Device (e.g. SD Card) Should Be Requisite Minimum
(Required)
2.
Must Not Create Files that Be Allowed to Read/Write Access from Other Applications (Required)
4.
3.
(Required)
(Required)
As mentioned in "4.6 Handling Files" and "4.6.1.3 Using Public Read/Write File," regardless of the
contents of the information to be stored, files should be set private, in principle. From Android
security designing point of view, exchanging information and its access control should be done in
Android system like Content Provider and Service, etc, and in case there's a reason that is impossible,
4.6.2.2. Must Not Create Files that Be Allowed to Read/Write Access from Other Applications
(Required)
As mentioned in "4.6.1.3 Using Public Read/Write File," when permitting other applications to
read/write files, information stored in files cannot be controlled. So, sharing information by using
read/write public files should not be considered from both security and function/designing points of
view.
4.6.2.3. Using Files Stored in External Device (e.g. SD Card) Should Be Requisite Minimum(Required)
As mentioned in "4.6.1.4 Using Eternal Memory (Read Write Public) File," storing files in external
memory device like SD card, leads to holding the potential problems from security and functional
points of view. On the other hand, SD card can handle files which have longer scope, compared with
application directory, and this is the only one storage that can be always used to bring out the data to
outside of application. So, there may be many cases that cannot help using it, depends on
application's spec.
When storing files in external memory device, considering unspecified large number of applications
and users can read/write/delete files, so it's necessary that application is designed considering the
points as per below as well as the points mentioned in sample code. In addition, regarding encryption
technology like encryption and electrical signature, it's planned that articles are published in future
edition of this Guidebook.
Sensitive information should not be saved in a file of external memory device, in principle.
In case sensitive information is saved in a file of external memory device, it should be encrypted.
255
In case saving in a file of external memory device information that will be trouble if it's tampered
When reading in files in external memory device, use data after verifying the safety of data to
read in.
Application should be designed supposing that files in external memory device can be always
deleted.
Please refer to "4.6.2.4 Application Should Be Designed Considering the Scope of File
4.6.2.4. Application Should Be Designed Considering the Scope of File
(Required)."
(Required)
Data saved in application directory is deleted by the following user operations. It's consistent with the
application's scope, and it's distinctive that it's shorter than the scope of application.
Uninstalling application.
Delete data and cache of each application (Setting > Apps > select target application.)
Files that were saved in external memory device like SD card, it's distinctive that the scope of the file
is longer than the scope of the application. In addition, the following situations are also necessary to
be considered.
As mentioned above, since scope of files are different depends on the file saving location, not only
from the viewpoint to protect sensitive information, but also form view point to achieve the right
behavior as application, it's necessary to select the file save location.
256
read/write files through file descriptors which are got by opening private files in Content Provider or
in Service.
Comparison between the file sharing method of direct access by other applications and the file
sharing method via file descriptor, is as per below Table 4.6-2. Variation of access permission and
range of applications that are permitted to access, can be considered as merits. Especially, from
security point of view, this is a great merit that, applicaions that are permitted to accesss can be
controlled in detail.
Read in
setting
Write in
Read in + Write in
permitted to access
permissions equally
Read in
Only add
Write in
Read in + Write in
This is common in both of above file sharing methods, when giving write permission for files to other
applications, integrity of file contents are difficult to be guaranteed. When several applications write
in in parallel, there's a risk that data structure of file contents are destroyed, and application doesn't
work normally. So, in sharing files with other applications, giving only read only permission is
preferable.
Herein below an implementation example of file sharing by Content Provider and its sample code, are
published.
Point
1.
2.
Even if it's a result from In house only Content Provider application, verify the safety of the result
data.
InhouseProvider.java
package org.jssec.android.file.inhouseprovider;
import java.io.File;
257
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import org.jssec.android.shared.SigPerm;
import org.jssec.android.shared.Utils;
import
import
import
import
import
import
android.content.ContentProvider;
android.content.ContentValues;
android.content.Context;
android.database.Cursor;
android.net.Uri;
android.os.ParcelFileDescriptor;
258
InhouseUserActivity.java
package org.jssec.android.file.inhouseprovideruser;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import org.jssec.android.shared.PkgCert;
import org.jssec.android.shared.SigPerm;
import org.jssec.android.shared.Utils;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
259
import android.content.pm.ProviderInfo;
import
import
import
import
import
android.net.Uri;
android.os.Bundle;
android.os.ParcelFileDescriptor;
android.view.View;
android.widget.TextView;
260
in a request.
ParcelFileDescriptor pfd = null;
try {
pfd = getContentResolver().openFileDescriptor(
Uri.parse("content://" + AUTHORITY), "r");
} catch (FileNotFoundException e) {
}
if (pfd != null) {
FileInputStream fis = new FileInputStream(pfd.getFileDescriptor());
if (fis != null) {
try {
byte[] buf = new byte[(int) fis.getChannel().size()];
fis.read(buf);
// *** POINT 2 *** Handle received result data carefully and securely,
// even though the data came from in-house applications.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely
."
logLine(new String(buf));
} catch (IOException e) {
} finally {
try {
fis.close();
} catch (IOException e) {
}
}
}
try {
pfd.close();
} catch (IOException e) {
}
} else {
logLine(" null file descriptor");
}
}
private TextView mLogView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mLogView = (TextView) findViewById(R.id.logview);
}
private void logLine(String line) {
mLogView.append(line);
mLogView.append("n");
}
}
261
the security for directory which is a file container. Herein below, security considerations of access
In Android, there are some methods to get/create subdirectory in application directory. The major
ones are as per below Table 4.6-3.
Context#getDir(String name,
int MODE)
other application
permission)
permission)
MODE_PRIVATE
MODE_WORLD_READABLE
MODE_WORLD_WRITABLE
Here especially what needs to pay attention is access permission setting by Context#getDir(). As
explained in file creation, basically directory also should be set private from the security designing
point of view. When sharing information depends on access permission setting, there may be an
This is a flag to give all applications read-only permission to directory. So all application can get
file list and individual file attribute information in the directory. Since secret files cannot be
placed under this directory, it's necessary to pay enough attention when using this flag.
MODE_WORLD_WRITABLE
This flag gives other applications write permission to directory. All applications can
create/move 8/rename/delete files in the directory. These operations has no relation with access
permission setting (Read/Write/Execute) of file itself, so it's necessary to pay attention that
operations can be done only with write permission to directory. Normally this flag should not be
used since file may be deleted/replaced freely by other applications.
Regarding Table 4.6-3 "Deletion by User," refer to "4.6.2.4 Application Should Be Designed
Files cannot be moved over mount point (e.g. from internal storage to external storage). Therefore,
moving the protected files from internal storage to external storage cannot be happened.
262
4.6.3.3. Access Permission Setting for Shared Preference and Database File
Shared Preference and database also consist of files. Regarding access permission setting, what are
explained for files are applied here. i.e., both Shared Preference and database, should be created as
private files same like files, and sharing contents should be achieved by the Android's
Herein below, the usage example of Shared Preference is shown. Shared Preference is crated as
263
This functionality is called 'Browsable Intent.' By specifying URI scheme in Manifest file, an application
responds the transition to the link (user tap etc) which has its URI scheme, and the application is
launched with the link as a parameter.
In addition, the method to launch the corresponding application from browser by using URI scheme
is supported not only in Android but also in iOS and other platforms, and this is generally used for
the linkage between Web application and external application, etc. For example, following URI
scheme is defined in Twitter application or Facebook application, and the corresponding applications
are launched from the browser both in Android and in iOS.
Table 4.7-1
URI scheme
Corresponding application
twitter://
fb://
It seems very convenient function considering the linkage and convenience, but there are some risks
that this function is abused by a malicious third party. What can be supposed are as follows, they
abuse application functions by preparing a malicious Web site with a link in which URL has incorrect
parameter, or they get information which is included in URL by cheating a smartphone owner into
installing the Malware which responds the same URI scheme.
There are some points to be aware when using 'Browsable Intent' against these risks.
4.7.1. Sample Code
Sample codes of an application which uses 'Browsable Intent' are shown below.
Points:
1.
2.
Starter.html
<html>
<body>
<!-- *** POINT 1 *** Sensitive information must not be included -->
<!-- Character strings to be passed as URL parameter, should be UTF-8 and URI encoded. -->
<a href="secure://jssec?user=user_id"> Login </a>
</body>
</html>
AndroidManifest.xml
264
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="8" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:name=".BrowsableIntentActivity"
android:label="@string/title_activity_browsable_intent" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
// Accept implicit Intent
<category android:name="android.intent.category.DEFAULT" />
// Accept Browsable intent
<category android:name="android.intent.category.BROWSABLE" />
// Accept URI 'secure://jssec'
<data android:scheme="secure" android:host="jssec"/>
</intent-filter>
</activity>
</application>
</manifest>
BrowsableIntentActivity.java
package org.jssec.android.browsableintent;
import
import
import
import
import
android.app.Activity;
android.content.Intent;
android.net.Uri;
android.os.Bundle;
android.widget.TextView;
265
266
(Webpage side) Sensitive Information Must Not Be Included in Parameter of Corresponding Link
2.
(Required)
(Required)
4.7.2.1. (Webpage side) Sensitive Information Must Not Be Included in Parameter of Corresponding Link
(Required)
When tapping the link in browser, an intent which has a URL value in its data (It can be retrieve by
Intent#getData) is issued, and an application which has a corresponding Intent Filter is launched
from Android system.
At this moment, when there are several applications which Intent Filter is set to receive the same URI
scheme, application selection dialogue is shown in the same way as normal launch by implicit Intent,
and an application which user selected is launched. In case that a Malware is listed in the selection of
application selection dialogue, there is a risk that user may launch the Malware by mistake and
parameters in URL are sent to Malware.
As per above, it is necessary to avoid from include sensitive information directly in URL parameter as
it is for creating general Webpage link since all parameters which are included in Webpage link URL
can be given to Malware.
In addition, there is a risk that user may launch a Malware and input password to it when it is defined
in specs that password input is executed in an application after being launched by 'Browsable Intent',
even if the URL parameter includes only non-sensitive information like User ID. So it should be
considered that specs like a whole Login process is completed within application side. It must be kept
in mind when designing an application and a service that launching application by 'Browsable Intent'
is equivalent to launching by implicit Intent and there is no guarantee that a valid application is
launched.
(Required)
URL parameters which are sent to an application are not always from a legitimate Web page, since a
link which is matched with URI scheme can be made by not only developers but anyone. In addition,
there is no method to verify whether the URL parameter is sent from a valid Web page or not.
So it is necessary to verify safety of a URL parameter before using it, e.g. check if an unexpected value
is included or not.
267
application log information are also output to LogCat. Log information in LogCat can be read out
from other application in the same device 9, so the application which outputs sensitive information to
Logcat, is considered that it has the vulnerability of the information leakage. The sensitive
From a security point of view, in release version application, it's preferable that any log should not be
output. However, even in case of release version application, log is output for some reasons in some
cases. In this chapter, we introduce some ways to output messages to LogCat in a safe manner even
in a release version application. Along with this explanation, please refer to "4.8.3.1 Two Ways of
Herein after, the method to control the Log output to LogCat by ProGuard in release version
application. ProGuard is one of the optimization tools which automatically delete the unnecessary
code like unused methods, etc.
There are five types of log output methods, Log.e(), Log.w(), Log.i(), Log.d(), Log.v(), in
android.util.Log class. Regarding log information, intentionally output log information (hereinafter
referred to as the Operation log information) should be distinguished from logging which is
inappropriate for a release version application such as debug log (hereinafter referred to as the
Development log information). It's recommended to use Log.e()/w()/i() for outputting operation log
information, and to use Log.d()/v() for outputting development log. Refer to "4.8.3.2 Selection
Standards of Log Level and Log Output Method" for the details of proper usage of five types of log
output methods, in addition, also refer to "4.8.3.3 DEBUG Log and VERBOSE Log Are Not Always
Deleted Automatically."
Here's an example of how to use LogCat in a safe manner. This example includes Log.d() and Log.v()
for outputting debug log. If the application is for release, these two methods would be deleted
automatically. In this sample code, ProGuard is used to automatically delete code blocks where
Log.d()/v() is called.
The log information output to LogCat can be read by applications that declare using READ_LOGS
permission. However, in Android 4.1 and later, log information that is output by other application
cannot be read. But smartphone user can read every log information ouput to logcat through ADB.
268
Points:
1.
3.
The return value of Log.d()/v()should not be used (with the purpose of substitution or
4.
When you build an application for release, you should bring the mechanism that automatically
2.
5.
comparison).
An APK file for the (public) release must be created in release build configurations.
ProGuardActivity.java
package org.jssec.android.log.proguard;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
public class ProGuardActivity extends Activity {
final static String LOG_TAG = "ProGuardActivity";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_proguard);
// *** POINT 1
Log.e(LOG_TAG,
Log.w(LOG_TAG,
Log.i(LOG_TAG,
// *** POINT 2
// *** POINT 3
arison).
Log.d(LOG_TAG,
Log.v(LOG_TAG,
}
}
A part of project.properties
# ProGuard
proguard.config=proguard-project.txt
proguard-project.txt
269
*** Point 5 *** An APK file for the (public) release must be created in release build configurations.
Figure 4.8-1 How to create release version application in Eclipse (By Export)
The difference of LogCat output between development version application (debug build) and release
version application (release build) are shown in below Figure 4.8-2.
Development version application (Debug build)
270
http://www.jssec.org/dl/android_securecoding_en.pdf
(Required)
2.
Construct the Build System to Auto-delete Codes which Output Development Log Information
3.
4.
(Recommended)
Use Only Methods of the android.util.Log Class for the Log Output
(Recommended)
(Recommended)
(Required)
Log which was output to LogCat can be read out from other applications, so sensitive information like
user's login information should not be output by release version application. It's necessary not to
write code which outputs sensitive information to log during development, or it's necessary to delete
To follow this rule, first, not to include sensitive information in operation log information. In addition,
it's recommended to construct the system to delete code which outputs sensitive information when
build for release. Please refer to "4.8.2.2 Construct the Build System to Auto-delete Codes which
(Recommended)."
4.8.2.2. Construct the Build System to Auto-delete Codes which Output Development Log Information
When Build for the Release
(Recommended)
When application development, sometimes it's preferable if sensitive information is output to log for
checking the process contents and for debugging, for example the interim operation result in the
process of complicated logic, information of program's internal state, communication data structure
of communication protocol. It doesn't matter to output the sensitive information as debug log during
developing, in this case, the corresponding log output code should be deleted before release, as
mentioned in "4.8.2.1 Sensitive Information Must Not Be Included in Operation Log Information
(Required)."
To delete surely the code which outputs development log information when release builds, the
system which executes code deletion automatically by using some tools, should be constructed.
ProGuard, which was described in "4.8.1 Sample Code," can work for this method. As described
below, there are some noteworthy points on deleting code by ProGuard. Here it's supposed to apply
the system to applications which output development log information by either of Log.d()/v(), based
ProGuard deletes unnecessary code like unused methods, automatically. By specifying Log.d()/v()as
parameter of -assumenosideeffects option, call for Log.d(),Log.v() are granted as unnecessary code ,
and those are to be deleted.
4.8 Outputting Log to LogCat
271
In case using this auto deletion system, pay attention that Log.v()/d() code is not deleted when using
returned value of Log.v(), Log.d(), so returned value of Log.v(), Log.d(), should not be used. For
example, Log.v() is not deleted in the next examination code.
If you'd like to reuse source code, you should keep the consistency of the project environment
including ProGuard settings. For example, source code that presupposes Log.d() and Log.v() are
deleted automatically by above ProGuard setting. If using this source code in another project which
ProGuard is not set, Log.d() and Log.v() are not to be deleted, so there's a risk that the sensitive
information may be leaked. When reusing source code, the consistency of project environment
including ProGuard setting should be secured.
(Recommended)
As mentioned in "4.8.1 Sample Code" and "4.8.3.2 Selection Standards of Log Level and Log Output
Method," sensitive information should not be output to log through Log.e()/w()/i(). On the other hand,
in order that a developer wants to output the details of program abnormality to log, when exception
occurs, stack trace is output to LogCat by Log.e(..., Throwable tr)/w(..., Throwable tr)/i(..., Throwable
tr), in some cases. However, sensitive information may sometimes be included in the stack trace
because it shows detail internal structure of the program. For example, when SQLiteException is
output as it is, what type of SQL statement is issued is clarified, so it may give the clue for SQL
injection attack. Therefore, it's recommended that use only Log.d()/Log.v() methods, when
outputting throwable object.
4.8.2.4. Use Only Methods of the android.util.Log Class for the Log Output
(Recommended)
You may output log by System.out/err to verify the application's behavior whether it works as
expected or not, during development. Of course, log can be output to LogCat by print()/println()
method of System.out/err, but it's strongly recommended to use only methods of android.util.Log
class, by the following reasons.
When outputting log, generally, use the most appropriate output method properly based on the
urgency of the information, and control the output. For example, categories like serious error,
caution, simple application's information notice, etc are to be used. However, in this case,
information which needs to be output at the time of release (operation log information) and
information which may include the sensitive information (development log information) are output
by the same method. So, it may happen that when delete code which outputs sensitive information,
272
only android.util.Log, what needs to be considered will increase, so it's in danger that some mistakes
may occur, like some deletion are dropped by oversight.
To decrease risk of above mentioned mistakes occurrence, it's recommended to use only methods of
android.util.Log class.
273
never be output, and another is necessary information for later analysis should be output as log. It's
favorable that any log should never be output in release version application from the security point of
view, but sometimes, log is output even in release version application for various reasons. Each way
of thinking is described as per below.
The former is "Any log should never be output," this is because outputting log in release version
application is not so much valuable, and there is a risk to leak sensitive information. This comes from
there's no method for developers to collect log information of the release version application in
Android application operation environment, which is different from many Web application operation
environments. Based on this thinking, the logging codes are used only in development phase, and all
the logging codes are deleted on building release version application.
The latter is "necessary information should be output as log for the later analysis," as a final option to
analyze application bugs in customer support, in case of any questions or doubt to your customer
support. Based on this idea, as introduced above, it is necessary to prepare the system that prevent
human errors and bring it in your project because if you don't have the system you have to keep in
mind to avoid logging the sensitive information in release version application.
For more details about logging method, refer to the following document.
Code Style Guidebook for Contributors / Log Sparingly
http://source.android.com/source/code-style.html#log-sparingly
android.util.Log class in Android. You should select the most appropriate method when using the
android.util.Log class to output log messages according to Table 4.8-1 which shows the selection
standards of logging levels and methods.
Table 4.8-1 Selection standards of log levels and log output method
Log level
ERROR
Method
Log.e()
WARN
Log.w()
INFO
Log.i()
274
Log.d()
VERBOSE
Log.v()
For more details about logging method, refer to the following document.
Code Style Guidebook for Contributors / Log Sparingly
http://source.android.com/source/code-style.html#log-sparingly
4.8.3.3. DEBUG Log and VERBOSE Log Are Not Always Deleted Automatically
The following is quoted from the developer reference of android.util.Log class 10.
The order in terms of verbosity, from least to most is ERROR, WARN, INFOFEBUG, VERBOSE. Verbose
should never be compiled into an application except during development. Debug logs are compiled
in but stripped at runtime. Error, warning and info logs are always kept.
After reading the above texts, some developers might have misunderstood the Log class behavior as
per below.
Log.v() call is not compiled when release build, VERBOSE log is never output.
Log.v() call is compiled, but DEBUG log is never output when execution.
However, logging methods never behave in above ways, and all messages are output regardless of
whether it is compiled with debug mode or release mode. If you read the document carefully, you will
be able to realize that the gist of the document is not about the behavior of logging methods but
basic policies for logging.
In this chapter, we introduced the sample code to get the expected result as described above by
10
http://developer.android.com/reference/android/util/Log.html
4.8 Outputting Log to LogCat
275
using ProGuard.
4.8.3.4. BuildConfig.DEBUG Should Be Used in ADT 21 or Later
Recent ADT plugin for Eclipse, the following BuildConfig.java file is automatically generated. The
following DEBUG consistent (BuildConfig.DEBUG) is automatically set as "false" in release build, and
as "true" in debug build, by ADT plugin.
BuildConfig.java
By using BuildConfig.DEBUG as per below, log output is restrained when release build.
if (BuildConfig.DEBUG) android.util.Log.d(TAG, "Log output information");
Unfortunately, there are some bugs in ADT20 and earlier, and DEBUG consistent became true even in
release build in some cases. However, these bugs are fixed in ADT 21, and it's necessary to use
BuildConfig.DEBUG with ADT 21 or later.
message (the first line of the code) even though it remove the statement of calling Log.d() method
(the second line of the code).
The following disassembly shows the result of release build of the code above with ProGuard.
Actually, there's no Log.d() call process, but you can see that character string consistence definition
like "Sensitive information1" and calling process of String#format() method, are not deleted and still
remaining there.
276
move-result-object v0
Actually, it's not easy to find the particular part that disassembled APK file and assembled log output
information as above. However, in some application which handles the very confidential information,
this type of process should not be remained in APK file in some cases.
You should implement your application like below to avoid such a consequence of remaining the
sensitive information in bytecode. In release build, the following codes are deleted completely by the
compiler optimization. However, you have to use BuildConfig.DEBUG with ADT 21 or later (Please
refer to "4.8.3.4 BuildConfig.DEBUG Should Be Used in ADT 21").
if (BuildConfig.DEBUG) {
String debug_info = String.format("%s:%s", " Snsitive information 1", "Sensitive information 2");
if (BuildConfig.DEBUG) android.util.Log.d(TAG, debug_info);
}
Besides, ProGuard cannot remove the log message of the following code ("result:" + value).
Log.d(TAG, "result:" + value);
In this case, you can solve the problem in the following manner.
if (BuildConfig.DEBUG) Log.d(TAG, "result:" + value);
System.out/err method outputs all messages to LogCat. Android could send some messages to
System.out/err even if developers did not use these methods in their code, for example, in the
(When the exception is not caught by application, it's given to Exception#printStackTrace() by the
system.)
You should handle errors and exceptions appropriately since the stack trace includes the unique
information of the application.
We introduce a way of changing default output destination of System.out/err. The following code
redirects the output of System.out/err method to nowhere when you build a release version
4.8 Outputting Log to LogCat
277
application. However, you should consider whether this redirection does not cause a malfunction of
application or system because the code temporarily overwrites the default behavior of
System.out/err method. Furthermore, this redirection is effective only to your application and is
worthless to system processes.
OutputRedirectApplication.java
package org.jssec.android.log.outputredirection;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import android.app.Application;
public class OutputRedirectApplication extends Application {
// PrintStream which is not output anywhere
private final PrintStream emptyStream = new PrintStream(new OutputStream() {
public void write(int oneByte) throws IOException {
// do nothing
}
});
@Override
public void onCreate() {
// Redirect System.out/err to PrintStream which doesn't output anywhere, when release build.
// Save original stream of System.out/err
PrintStream savedOut = System.out;
PrintStream savedErr = System.err;
// Once, redirect System.out/err to PrintStream which doesn't output anywhere
System.setOut(emptyStream);
System.setErr(emptyStream);
// Restore the original stream only when debugging. (In release build, the following 1 line is deleted byProGu
ard.)
resetStreams(savedOut, savedErr);
}
// All of the following methods are deleted byProGuard when release.
private void resetStreams(PrintStream savedOut, PrintStream savedErr) {
System.setOut(savedOut);
System.setErr(savedErr);
}
}
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.jssec.android.log.outputredirection"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="8" />
<application
278
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:name=".OutputRedirectApplication" >
<activity
android:name=".LogActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
project.properties
# ProGuard
proguard.config=proguard-project.txt
proguard-project.txt
The difference of LogCat output between development version application (debug build) and release
version application (release build) are shown as per below Figure 4.8-3.
Development version application (Debug build)
279
can easily show web site and html file by it. And also we need to consider risk from WebView's
remarkable function; such as JavaScript-Java object bind.
Especially what we need to pay attention is JavaScript. (Please note that JavaScript is disabled as
Figure 4.9-1 shows flow chart to choose sample code according to content characteristic.
11Strictly
speaking, you can enable JavaScript if we can say the content is safe. If the contents are
managed in house, the contents should be guaranteed of security. And the company can secure them.
In other words, we need to have business representations decision to enable JavaScript for other
companys contents. The contents which are developed by trusted partner might have security
guarantee. But there is still potential risk. Therefore the decision is needed by responsible person.
280
http://www.jssec.org/dl/android_securecoding_en.pdf
Start
No
Yes
Application only accesses to
contents which are managed
in-house onlyY?
No
Yes
281
4.9.1.1. Show Only Contents Stored under assets/res Directory in the APK
You can enable JavaScript if your application shows only contents stored under assets/ and res/
directory in apk.
The following sample code shows how to use WebView to show contents stored under assets/ and
res/.
Points:
1.
Disable to access files (except files under assets/ and res/ in apk).
2.
WebViewAssetsActivity.java
package org.jssec.webview.assets;
import
import
import
import
android.app.Activity;
android.os.Bundle;
android.webkit.WebSettings;
android.webkit.WebView;
282
http://www.jssec.org/dl/android_securecoding_en.pdf
service and your Android application can take proper actions to secure both of them.
As Figure 4.9-2 shows, your web service can only refer to contents which are managed in-house.
In addition, the web service is needed to take appropriate security action. Because there is
potential risk if contents which your web service refers to may have risk; such as malicious attack
code injection, data manipulation, etc.
Please refer to "4.9.2.1 Enable JavaScript Only If Contents Are Managed In-house
(Required)."
Using HTTPS, the application should establish network connection to your managed web service
only if the certification is trusted.
The following sample code is an activity to show contents which are managed in-house.
Contents
Reference relationship of contents
Application
Access by application
Not allowed
Android Device
In-house services
Services/Contents
in Internet
Figure 4.9-2 Accessible contents and Non-accessible contents from application
283
Points:
1.
2.
4.
3.
WebViewTrustedContentsActivity.java
package org.jssec.webview.trustedcontents;
import
import
import
import
import
import
import
import
import
android.app.Activity;
android.app.AlertDialog;
android.content.DialogInterface;
android.net.http.SslCertificate;
android.net.http.SslError;
android.os.Bundle;
android.webkit.SslErrorHandler;
android.webkit.WebView;
android.webkit.WebViewClient;
284
285
The following sample code is an activity to show contents which are not managed in-house.
This sample code shows contents specified by URL which user inputs through address bar. Please
note that JavaScript is disabled and connection is aborted when SSL error occurs. The error handling
is the same as "4.9.1.2 Show Only Contents which Are Managed In-house" for the details of HTTPS
communication. Please refer to "5.4 Communicating via HTTPS" for the details also.
Points:
1.
2.
WebViewUntrustActivity.java
package org.jssec.webview.untrust;
import
import
import
import
import
import
import
import
import
import
import
import
import
android.app.Activity;
android.app.AlertDialog;
android.content.DialogInterface;
android.graphics.Bitmap;
android.net.http.SslCertificate;
android.net.http.SslError;
android.os.Bundle;
android.view.View;
android.webkit.SslErrorHandler;
android.webkit.WebView;
android.webkit.WebViewClient;
android.widget.Button;
android.widget.EditText;
286
287
288
(Required)
2.
(Required)
4.
(Required)
3.
Disable JavaScript to Show URLs Which Are Received through Intent, etc.
(Required)
(Required)
What we have to pay attention on WebView is whether we enable the JavaScript or not. As principle,
we can only enable the JavaScript only IF the application will access to services which are managed
in-house. And you must not enable the JavaScript if there is possibility to access services which are
not managed in-house.
In case that application accesses contents which are developed IN HOUSE and are distributed
through servers which are managed IN HOUSE, we can say that the contents are ONLY modified
by your company. In addition, it is also needed that each content refers to only contents stored in
the servers which have proper security.
In this scenario, we can enable JavaScript on the WebView. Please refer to "4.9.1.2 Show Only
And you can also enable JavaScript if your application shows only contents stored under assets/
and res/ directory in the apk. Please refer to "4.9.1.1 Show Only Contents Stored under
assets/res Directory" also.
You must NOT think you can secure safety on contents which are NOT managed IN HOUSE.
Therefore you have to disable JavaScript. Please refer to "4.9.1.3 Show Contents which Are Not
Managed In-house."
In addition, you have to disable JavaScript if the contents are stored in external storage devices;
such as microSD because other application can modify the contents.
(Required)
You have to use HTTPS to communicate to servers which are managed in-house because there is
potential risk of spoofing the services by malicious third party.
Please refer to both "4.9.2.4 Handle SSL Error Properly (Required)," and "5.4 Communicating via
HTTPS" .
289
4.9.2.3. Disable JavaScript to Show URLs Which Are Received through Intent, etc.
(Required)
Don't enable JavaScript if your application needs to show URLs which are passed from other
application as Intent, etc. Because there is potential risk to show malicious web page with malicious
JavaScript.
Sample code in the section "4.9.1.2 Show Only Contents which Are Managed In-house," uses fixed
value URL to show contents which are managed in-house, to secure safety.
If you need to show URL which is received from Intent, etc, you have to confirm that URL is in
managed URL in-house. In short, the application has to check URL with white list which is regular
expression, etc. In addition, it should be HTTPS.
(Required)
You have to terminate the network communication and inform error notice to user when SSL error
happens on HTTPS communication.
SSL error shows invalid server certification risk or MTIM (man-in-the-middle attack) risk. Please note
that WebView has NO error notice mechanism regarding SSL error. Therefore your application has to
show the error notice to inform the risk to the user. Please refer to sample code in the section of
"4.9.1.2 Show Only Contents which Are Managed In-house," and "4.9.1.3 Show Contents which Are
Not Managed In-house".
In addition, your application MUST terminate the communication with the error notice.
we need to add is to show SSL error notice. And then we can handle SSL error properly.
290
efficiently and loophole will be prepared. This chapter will explain how to use the security functions
properly.
described here. Only what is related to password input is mentioned, here. Regarding how to save
password, another articles is planned to be published is future edition.
Figure 5.1-1
Points:
1.
3.
2.
291
Points: When handling the last Input password, pay attention the following points along with the
above points.
4.
In the case there is the last input password in an initial display, display the fixed digit numbers of
black dot as dummy in order not that the digits number of last password is guessed.
5.
When the dummy password is displayed and the "Show password" button is pressed, clear the
last input password and provide the state for new password input.
6.
When last input password is displayed with dummy, in case user tries to input password, clear
the last input password and treat new user input as a new password.
password_activity.xml
292
android:onClick="onClickCancelButton"
android:text="@android:string/cancel" />
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="onClickOkButton"
android:text="@android:string/ok" />
</LinearLayout>
</LinearLayout>
Implementation for 3 methods which are located at the bottom of PasswordActivity.java, should be
PasswordActivity.java
package org.jssec.android.password.passwordinputui;
import
import
import
import
import
import
import
import
import
import
import
android.app.Activity;
android.os.Bundle;
android.text.Editable;
android.text.InputType;
android.text.TextWatcher;
android.view.View;
android.widget.CheckBox;
android.widget.CompoundButton;
android.widget.CompoundButton.OnCheckedChangeListener;
android.widget.EditText;
android.widget.Toast;
293
if (getPreviousPassword() != null) {
// *** POINT 4 *** In the case there is the last input password in an initial display,
// display the fixed digit numbers of black dot as dummy in order not that the digits number of last passw
ord is guessed.
// Display should be dummy password.
mPasswordEdit.setText("**********");
// To clear the dummy password when inputting password, set text change listener.
mPasswordEdit.addTextChangedListener(new PasswordEditTextWatcher());
// Set dummy password flag
mIsDummyPassword = true;
}
// Set a listner to change check state of password display option.
mPasswordDisplayCheck
.setOnCheckedChangeListener(new OnPasswordDisplayCheckedChangeListener());
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Unnecessary when specifying not to regenerate Activity by the change in screen aspect ratio.
// Save Activity state
outState.putBoolean(KEY_DUMMY_PASSWORD, mIsDummyPassword);
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// Unnecessary when specifying not to regenerate Activity by the change in screen aspect ratio.
// Restore Activity state
mIsDummyPassword = savedInstanceState.getBoolean(KEY_DUMMY_PASSWORD);
}
/**
* Process in case password is input
*/
private class PasswordEditTextWatcher implements TextWatcher {
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
// Not used
}
public void onTextChanged(CharSequence s, int start, int before,
int count) {
// *** POINT 6 *** When last Input password is displayed as dummy, in the case an user tries to input pass
word,
// Clear the last Input password, and treat new user input as new password.
if (mIsDummyPassword) {
// Set dummy password flag
mIsDummyPassword = false;
// Trim space
CharSequence work = s.subSequence(start, start + count);
mPasswordEdit.setText(work);
// Cursor position goes back the beginning, so bring it at the end.
mPasswordEdit.setSelection(work.length());
}
294
}
public void afterTextChanged(Editable s) {
// Not used
}
}
/**
* Process when check of password display option is changed.
*/
private class OnPasswordDisplayCheckedChangeListener implements
OnCheckedChangeListener {
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
// *** POINT 5 *** When the dummy password is displayed and the "Show password" button is pressed,
// clear the last input password and provide the state for new password input.
if (mIsDummyPassword && isChecked) {
// Set dummy password flag
mIsDummyPassword = false;
// Set password empty
mPasswordEdit.setText(null);
}
// Cursor position goes back the beginning, so memorize the current cursor position.
int pos = mPasswordEdit.getSelectionStart();
// *** POINT 2 *** Provide the option to display the password in a plain text
// Create InputType
int type = InputType.TYPE_CLASS_TEXT;
if (isChecked) {
// Plain display when check is ON.
type |= InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
} else {
// Masked display when check is OFF.
type |= InputType.TYPE_TEXT_VARIATION_PASSWORD;
}
// Set InputType to password EditText
mPasswordEdit.setInputType(type);
// Set cursor position
mPasswordEdit.setSelection(pos);
}
}
// Implement the following method depends on application
/**
* Get the last Input password
*
* @return Last Input password
*/
private String getPreviousPassword() {
// When need to restore the saved password, return password character string
// For the case password is not saved, return null
return "hirake5ma";
}
295
/**
* Process when cancel button is clicked
*
* @param view
*/
public void onClickCancelButton(View view) {
// Close Activity
finish();
}
/**
* Process when OK button is clicked
*
* @param view
*/
public void onClickOkButton(View view) {
// Execute necessary processes like saving password or using for authentication
String password = null;
if (mIsDummyPassword) {
// When dummy password is displayed till the final moment, grant last iInput password as fixed password.
password = getPreviousPassword();
} else {
// In case of not dummy password display, grant the user input password as fixed password.
password = mPasswordEdit.getText().toString();
}
// Display password by Toast
Toast.makeText(this, "password is "" + password + """,
Toast.LENGTH_SHORT).show();
// Close Activity
finish();
}
}
296
2.
4.
When Displaying the Last Input Password, Dummy Password Must Be Displayed
3.
(Required)
(Required)
(Required)
(Required)
(Required)
Smartphone is often used in crowded places like in a train or in a bus, and the risk that password is
peeked by someone. So the function to mask display password is necessary as an application spec.
There are 2 methods to mask display EditText which password is input, one is to specify by layout
XML statically and another is to switch in a program dynamically. The former one can be achieved by
297
(Required)
Password input in Smartphone is done by touch panel input, so compared with keyboard input in PC,
miss input may be easily happened. Because of the inconvenience of inputting, user may use the
simple password, and it makes more dangerous. In addition, when there's a policy like account is
locked due the several times of password input failure, it's necessary to avoid from miss input as
much as possible. As a solution of these problems, by preparing an option to display password in a
plain text, user can use the safe password.
However, when displaying password in a plain text, it may be sniffed, so when using this option. It's
necessary to call user cautions for sniffing from behind. In addition, in case option to display in a
plain text is implemented, it's also necessary to prepare the system to auto cancel the plain text
display like setting the time of plain display. The restrictions for password plain text display are
published in another article in future edition. So, the restrictions for password plain text display are
not included in sample code.
Show check ON
Figure 5.1-2
By specifying InputType of EditText, mask display and plain text display can be switched.
PasswordActivity.java
/**
* Process when check of password display option is changed.
*/
private class OnPasswordDisplayCheckedChangeListener implements
OnCheckedChangeListener {
298
(Required)
To prevent it from a password peeping out, the default value of password display option, should be
set OFF, when Activity is launched. The default value should be always defined as safer side,
basically.
5.1.2.4. When Displaying the Last Input Password, Dummy Password Must Be Displayed(Required)
When specifying the last input password, not to give the third party any hints for password, it should
be displayed as dummy with the fixed digits number of mask characters (* etc). In addition, in the
case pressing "Show password" when dummy display, clear password and switch to plain text display
mode. It can help to suppress the risk that the last input password is sniffed low, even if the device is
passed to a third person like when it's stolen. FYI, In case of dummy display and when a user tries to
input password, dummy display should be cancelled, it necessary to turn the normal input state.
299
In the case of dummy display, when password display option is turned ON, clear the displayed
contents.
PasswordActivity.java
/**
* Process when check of password display option is changed.
*/
private class OnPasswordDisplayCheckedChangeListener implements
OnCheckedChangeListener {
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
// *** POINT 5 *** When the dummy password is displayed and the "Show password" button is pressed,
// Clear the last input password and provide the state for new password input.
if (mIsDummyPassword && isChecked) {
// Set dummy password flag
mIsDummyPassword = false;
// Set password empty
mPasswordEdit.setText(null);
}
300
- Abbreviation }
}
In case of dummy display, when user tries to input password, clear dummy display.
PasswordActivity.java
301
/**
* Process when inputting password.
*/
private class PasswordEditTextWatcher implements TextWatcher {
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
// Not used
}
public void onTextChanged(CharSequence s, int start, int before,
int count) {
// *** POINT 6 *** When last Input password is displayed as dummy, in the case an user tries to input pass
word,
// Clear the last Input password, and treat new user input as new password.
if (mIsDummyPassword) {
// Set dummy password flag
mIsDummyPassword = false;
// Trim space
CharSequence work = s.subSequence(start, start + count);
mPasswordEdit.setText(work);
// Cursor position goes back the beginning, so bring it at the end.
mPasswordEdit.setSelection(work.length());
}
}
public void afterTextChanged(Editable s) {
// Not used
}
}
302
In login process, need to input 2 information which is ID(account) and password. When login
failure, there are 2 cases. One is ID doesn't exist. Another is ID exists but password is incorrect.
If either of these 2 cases is distinguished and displayed in a login failure message, attackers can
guess whether the specified ID exists or not. To stop this kind of guess, these 2 cases should not
be specified in login failure message, and this message should be displayed as per below.
Message example: Login ID or password is incorrect.
Auto Login function
There is a function to perform auto login by omitting login ID/password input in the next time
and later, after successful login process has been completed once. Auto login function can omit
the complicated input. So the convenience will increase, but on the other hand, when a
Smartphone is stolen, the risk which is maliciously being used by the third party, will follow.
Only the use when damages caused by the malicious third party is somehow acceptable, or only
in the case enough security measures can be taken, auto login function can be used. For example,
in the case of online banking application, when the device is operated by the third party, financial
damage may be caused. So in this case, security measures are necessary along with auto login
function. There are some possible counter-measures, like [Require re-inputting password just
before financial process like payment process occurs], [When setting auto login, call a user for
enough attentions and prompt user to secure device lock], etc. When using auto login, it's
necessary to investigate carefully considering the convenience and risks along with the assumed
counter measures.
Current password
New password
When auto login function is introduced, there are possibilities that third party can use an application.
In that case, to avoid from changing password unexpectedly, it's necessary to require the current
password input. In addition, to decrease the risk of getting into unserviceable state due to miss
5.1 Creating Password Input Screens
303
inputting new password, it's necessary to require new password input 2 times.
5.1.3.3. Regarding "Make passwords visible" Setting
There is a setting in Android's setting menu, called "Make passwords visible." In case of Android 4.4,
Figure 5.1-3
When turning ON "Make passwords visible" setting, the last input character is displayed in a plain text.
After the certain time (about 2 seconds) passed, or after inputting the next character, the characters
which was displayed in a plain text is masked. When turning OFF, it's masked right after inputting.
This setting affects overall system, and it's applied to all applications which use password display
function of EditText.
304
Figure 5.1-4
305
permission in order to access them. When an application, which has declared a permission that needs
Figure 5.2-1
From this confirmation screen, a user is able to know which types of features and/or information an
application is trying to access. If the behavior of an application is trying to access features and/or
information that are clearly unnecessary, then there is a high possibility that the application is a
Points:
1.
2.
AndroidManifest.xml
307
more precise), you can build a mechanism where only communications between in-house
applications is permitted. By providing the composite function based on inter-application
communication between multiple in-house applications, the applications get more attractive and
your business could get more profitable by selling them as series. It is a case of using
The sample application "In-house-defined Signature Permission (UserApp)" launchs the sample
method. Both applications need to be signed with the same developer key. If keys for signing them
are different, the UserApp sends no Intent to the ProtectedApp, and the ProtectedApp processes no
Intent received from the UserApp. Furthermore, it prevents malwares from circumventing your own
signature permission using the matter related to the installation order as explained in the Advanced
section.
2.
3.
5.
When exporting an APK from Eclipse, sign the APK with the same developer key that applications
4.
At run time, verify if the signature permission is defined by itself on the program code.
AndroidManifest.xml
308
ProtectedActivity.java
package org.jssec.android.permission.protectedapp;
import org.jssec.android.shared.SigPerm;
import org.jssec.android.shared.Utils;
import
import
import
import
android.app.Activity;
android.content.Context;
android.os.Bundle;
android.widget.TextView;
309
setContentView(R.layout.main);
mMessageView = (TextView) findViewById(R.id.messageView);
// *** POINT 4 *** At run time, verify if the signature permission is defined by itself on the program code
if (!SigPerm.test(this, MY_PERMISSION, myCertHash(this))) {
mMessageView.setText("In-house defined signature permission is not defined by in-house application.");
return;
}
// *** POINT 4 *** Continue processing only when the certificate matches
mMessageView.setText("In-house-defined signature permission is defined by in-house application, was confirmed
.");
}
}
SigPerm.java
package org.jssec.android.shared;
import
import
import
import
android.content.Context;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
android.content.pm.PermissionInfo;
PkgCert.java
package org.jssec.android.shared;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
310
import
import
import
import
import
android.content.Context;
android.content.pm.PackageInfo;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
android.content.pm.Signature;
311
*** Point 5 *** When exporting an APK from Eclipse, sign the APK with the same developer key that
applications using the component have used.
Figure 5.2-3
Points:Application Using Component
6.
The same signature permission that the application uses must not be defined.
8.
Verify if the in-house signature permission is defined by the application that provides the
7.
9.
11. When exporting an APK from Eclipse, sign the APK with the same developer key that the
destination application uses.
AndroidManifest.xml
312
</manifest>
UserActivity.java
package org.jssec.android.permission.userapp;
import org.jssec.android.shared.PkgCert;
import org.jssec.android.shared.SigPerm;
import org.jssec.android.shared.Utils;
import
import
import
import
import
import
android.app.Activity;
android.content.Context;
android.content.Intent;
android.os.Bundle;
android.view.View;
android.widget.Toast;
313
PkgCertWhitelists.java
package org.jssec.android.shared;
import java.util.HashMap;
import java.util.Map;
import android.content.Context;
public class PkgCertWhitelists {
private Map<String, String> mWhitelists = new HashMap<String, String>();
public boolean add(String pkgname, String sha256) {
if (pkgname == null) return false;
if (sha256 == null) return false;
sha256 = sha256.replaceAll(" ", "");
if (sha256.length() != 64) return false;
// SHA-256 -> 32 bytes -> 64 chars
sha256 = sha256.toUpperCase();
if (sha256.replaceAll("[0-9A-F]+", "").length() != 0) return false; // found non hex char
mWhitelists.put(pkgname, sha256);
return true;
}
public boolean test(Context ctx, String pkgname) {
// Get the correct hash value which corresponds to pkgname.
String correctHash = mWhitelists.get(pkgname);
// Compare the actual hash value of pkgname with the correct hash value.
return PkgCert.test(ctx, pkgname, correctHash);
}
}
PkgCert.java
package org.jssec.android.shared;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
314
import
import
import
import
import
android.content.Context;
android.content.pm.PackageInfo;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
android.content.pm.Signature;
315
*** Point 11 *** When exporting an APK from Eclipse, sign the APK with the same developer key that
the destination application uses.
Figure 5.2-4
316
appears at different points in this Guidebook. Strictly speaking, the hash value means "the SHA256
hash value of the public key certificate for the developer key used to sign the APK."
Using a program called keytool that is bundled with JDK, you can get the hash value (also known
as certificate fingerprint) of a public key certificate for the developer key. There are various hash
methods such as MD5, SHA1, and SHA256 due to the differences in hash algorithm. However,
considering the security strength of the encryption bit length, this Guidebook recommends the
use of SHA256. Unfortunately, the keytool bundled to JDK6 that is used in Android SDK does not
support SHA256 for calculating hash values. Therefore, it is necessary to use the keytool that is
bundled to JDK7.
*******************************************
*******************************************
Without installing JDK7, you can easily verify the certificate hash value by using JSSEC Certificate
Hash Value Checker.
317
Figure 5.2-5
This is an Android application that displays a list of certificate hash values of applications which
are installed in the device. In the Figure above, the 64-character hexadecimal notation string that
is shown on the right of "sha-256" is the certificate hash value. The sample code folder, "JSSEC
CertHash Checker" that comes with this Guidebook is the set of source codes. If you would like,
318
System Dangerous Permissions of Android OS Must Only Be Used for Protecting User Assets
2.
3.
(Required)
(Required)
Your Own Signature Permission Must Only Be Defined on the Provider-side Application
(Required)
4.
5.
(Recommended)
Application
(Recommended)
6.
(Required)
The String for Your Own Permission Name Should Be of an Extent of the Package Name of
5.2.2.1. System Dangerous Permissions of Android OS Must Only Be Used for Protecting User Assets
(Required)
Since the use of your own dangerous permission is not recommended (please refer to "5.2.2.2 Your
Own Dangerous Permission Must Not Be Used (Required)", we will proceed on the premise of using
system dangerous permission of Android OS.
Unlike the other three types of permissions, dangerous permission has a feature that requires the
user's consent to the grant of the permission to the application. When installing an application on a
device that has declared a dangerous permission to use, the following screen will be displayed.
Subsequently, the user is able to know what level of permission (dangerous permission and normal
permission) the application is trying to use. When the user taps "install," the application will be
granted the permission and then it will be installed.
319
Figure 5.2-6
An application can handle user assets and assets that the developer wants to protect. We must be
aware that dangerous permission can protect only user assets because the user is just who the
granting of permission is entrusted to. On the other hand, assets that the developer wants to protect
cannot be protected by the method above.
For example, suppose that an application has a Component that communicates only with an
In-house application, it doesn't permit the access to the Component from any applications of the
other companies, and it is implemented that it's protected by dangerous permission. When a user
grants permission to an application of another company based on the user's judgment, in-house
assets that need to be protected may be exploited by the application granted. In order to provide
protection for in-house assets in such cases, we recommend the usage of in-house-defined
signature permission.
5.2.2.2. Your Own Dangerous Permission Must Not Be Used
(Required)
Even when in-house-defined Dangerous Permission is used, the screen prompt "Asking for the
Allowance of Permission from User" is not displayed in some cases. This means that at times the
feature that asks for permission based on the judgment of a user, which is the characteristic of
Dangerous Permission, does not function. Accordingly, the Guidebook will make the rule "In-house
-defined dangerous permission must not be used."
In order to explain it, we assume two types of applications. The first type of application defines a
in-house dangerous permission, and it is an application that makes a Component, which is protected
by this permission, public. We call this ProtectedApp. The other is another application which we call
AttackerApp and it tries to exploit the Component of ProtectedApp. Also we assume that the
320
AttackerApp not only declares the permission to use it, but also defines the same permission.
AttackerApp can use the Component of a ProtectedApp without the consent of a user in the following
cases:
1.
2.
3.
When the user installs the AttackerApp, the installation will be completed without the screen
prompt that asks for the user to grant the application the dangerous permission.
Similarly, when the user installs the ProtectedApp, the installation will be completed without any
special warnings.
When the user launches the AttackerApp afterwards, the AttackerApp can access the Component
of the ProtectedApp without being detected by the user, which can potentially lead to damage.
The cause of this case is explained in the following. When the user tries to install the AttackerApp
first, the permission that has been declared for usage with uses-permission is not defined on the
particular device yet. Finding no error, Android OS will continue the installation. Since the user
consent for dangerous permission is required only at the time of installation, an application that has
already been installed will be handled as if it has been granted permission. Accordingly, if the
Component of an application which is installed later is protected with the dangerous permission of
the same name, the application which was installed beforehand without the user permission will be
able to exploit the Component.
Furthermore, since the existence of system dangerous permissions defined by Android OS is
guaranteed when an application is installed, the user verification prompt will be displayed every time
an application with uses-permission is installed. This problem arises only in the case of self-defined
dangerous permission.
At the time of this writing, no viable method to protect the access to the Component in such cases
has been developed yet. Therefore, your own dangerous permission must not be used.
5.2.2.3. Your Own Signature Permission Must Only Be Defined on the Provider-side Application
(Required)
As demonstrated in, "5.2.1.2 How to Communicate Between In-house Applications with
In-house-defined Signature Permission," the security can be assured by checking the signature
permission at the time of executing inter-communications between In-house applications. When
using this mechanism, the definition of the permission whose Protection Level is signature must be
written in AndroidManifest.xml of the provider-side application that has the Component, but the
user-side application must not define the signature permission.
The reason for this is as follows.
We assume that there are multiple user-side applications that have been installed prior to the
provider-side application and every user-side application not only has required the signature
permission that the provider-side application has defined, but also has defined the same permission.
Under these circumstances, all user-side applications will be able to access the provider-side
application just after the provider-side application is installed. Subsequently, when the user-side
application that was installed first is uninstalled, the definition of the permission also will be deleted
and then the permission will turn out to be undefined. As a result, the remaining user-side
5.2 Permission and Protection Level
321
Before processing a request to the Component, first verify that the in-house-defined signature
permission has been defined by an in-house application. If not, ignore the request. (protection
If utilizing the protection provided by signature permission for devices Android 2.1 or earlier (outside
of the scope of this Guidebook), or If using normal/dangerous permission, the permission will not be
granted the user-side application if the user-side application is installed before the provider-side
application, the permission remains undefined. Therefore, the Component cannot be accessed even
after the provider-side application has been installed.
12
322
5.
Sign APKs of all inter-communicating applications with the same developer key.
Here, for specific points on how to implement "Verify that the in-house-defined signature
permission has been defined by an In house application", please refer to "5.2.1.2 How to
Communicate Between In-house Applications with In-house-defined Signature Permission".
5.2.2.5. Your Own Normal Permission Should Not Be Used
(Recommended)
whether an application can be granted the permission depends on the order of installation. For
example, when you install an application (user-side) that has declared to use a normal permission
prior to another application (provider-side) that possesses a Component which has defined the
permission, the user-side application will not be able to access the Component protected with the
As a way to prevent the loss of inter-application communication due to the order of installation, you
may think of defining the permission in every application in the communication. By this way, even if
an user-side application has been installed prior to the provider-side application, all user-side
applications will be able to access the provider-side application. However, it will create a situation
that the permission is undefined when the user-side application installed first is uninstalled. As a
result, even if there are other user-side applications, they will not be able to gain access to the
provider-side application.
As stated above, there is a concern of damaging the availability of an application, thus your own
The String for Your Own Permission Name Should Be of an Extent of the Package Name of
Application
(Recommended)
When multiple applications define permissions under the same name, the Protection Level that has
been defined by an application installed first will be applied. Protection by signature permission will
not be available in the case that the application installed first defines a normal permission and the
application installed later defines a signature permission under the same name. Even in the absence
of malicious intent, a conflict of permission names among multiple applications could cause behavior
s of any applications as an unintended Protection Level. To prevent such accidents, it is
recommended that a permission name extends (starts with) the package name of the application
323
For example, the following name would be preferred when defining a permission of READ access for
the package of org.jssec.android.sample.
org.jssec.android.sample.permission.READ
324
between the applications signed with the same developer key. Since a developer key is a private key
and must not be public, there is a tendency to use signature permission for protection only in cases
where in-house applications communicate with each other.
First, we will describe the basic usage of self-defined signature permission that is explained in the
Developer Guide (http://developer.android.com/guide/topics/security/security.html) of Android.
However, as it will be explained later, there are problems with regard to the avoidance of permission.
Consequently, counter-measures that are described in this Guidebook are necessary.
The followings are the basic usage of self-defined Signature Permission.
1.
2.
3.
4.
Actually, if the following conditions are fullfilled, this approach will create a loophole to avoid
signature permission from being performed.
For the sake of explanation, we call an application that is protected by self-defined signature
permission as ProtectedApp, and AttackerApp for an application that has been signed by a different
developer key from the ProtectedApp. What a loophole to avoid signature permission from being
performed means is, despite the mismatch of the signature for AttackerApp, it is possible to gain
access to the Component of ProtectedApp.
Condition 1. An AttackerApp also defines a normal permission (strictly speaking, signature
permission is also acceptable) under the same name as the signature permission which has been
defined by the ProtectedApp.
Example: <permission android:name=" xxx" android:protectionLevel="normal" />
Condition 2. The AttackerApp declares the self-defined normal permission with uses-permission.
Example: <uses-permission android:name="xxx" />
Condition 3. The AttackerApp has installed on the device prior to the ProtectedApp.
325
http://www.jssec.org/dl/android_securecoding_en.pdf
Condition 3
ProtectedApp
Install before
ProtectedApp
AttackerApp
Condition 1
AndroidManifest.xml
AndroidManifest.xml
<permission
android:name=xxx
android:protectionLevel=signature
/>
<permission
android:name=xxx
android:protectionLevel=normal
/>
<activity
android:permission=xxx >
</activity>
Figure 5.2-7
The permission name that is necessary to meet Condition 1 and Condition 2 can easily be known by
an attacker taking AndroidManifest.xml out from an APK file. The attacker also could satisfy
Condition 3 with a certain amount of effort (e.g. deceiving a user).
There is a risk of self-defined signature permission to evade protection if only the basic usage is
adopted, and a counter-measure to prevent such loopholes is needed. Specificcally, you could find
how to solve the above-mentioned issues by using the method described in "5.2.2.4 Verify If the
In-house-defined Signature Permission Is Defined by an In-house Application".
5.2.3.2. Falsification of AndroidManifest.xml by a User
We have already touched on the case that a Protection Level of self-defined permission could be
changed as not intended. To prevent malfunctioning due to such cases, it has been needed to
implement some sort of counter-measures on the source-code side of Java. From the viewpoint of
AndroidManifest.xml falsification, we will talk about the counter-measures to be taken on the
source-code side. We will demonstrate a simple case of installation that can detect falsifications.
However, please note that these counter-measures are little effective against professional hackers
This section is about the falsification of an application and users with malicious intent. Although this
is originally outside of the scope of a Guidebook, from the fact that this is related to Permission and
the tools for such falsification are provided in public as Android applications, we decided to mention
it as "Simple counter-measures against amateur hackers".
It must be remembered that applications that can be installed from Google Play are applications that
326
can be falsified without root privilege. The reason is that applications that can rebuild and sign APK
files with altered AndroidManifest.xml are distributed. By using these applications, anyone can delete
any permission from applications they have installed.
As an example, there seems to be cases of rebuilding APKs with different signatures altering
AndroidManifest.xml with INTERNET permission removed to render advertising modules attached in
applications as useless. There are some users who praise these types of tools due to the fact that no
personal information is leaked anywhere. As these ads which are attached in applications stop
functioning, such actions cause monetary damage for developers who are counting on ad revenue.
And it is believed that most of the users don't have any compunction.
In the following code, we show an instance of implementation that an application that has declared
INTERNET permission with uses-permission verifies if INTERNET permission is described in the
AndroidManifest.xml of itself at run time.
public class CheckPermissionActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Acquire Permission defined in AndroidManifest.xml
List<String> list = getDefinedPermissionList();
// Detect falsification
if( checkPermissions(list) ){
// OK
Log.d("dbg", "OK.");
}else{
Log.d("dbg", "manifest file is stale.");
finish();
}
}
/**
* Acquire Permission through list that was defined in AndroidManifest.xml
* @return
*/
private List<String> getDefinedPermissionList(){
List<String> list = new ArrayList<String>();
list.add("android.permission.INTERNET");
return list;
}
/**
* Verify that Permission has not been changed Permission
* @param permissionList
* @return
*/
private boolean checkPermissions(List<String> permissionList){
try {
PackageInfo packageInfo = getPackageManager().getPackageInfo(
getPackageName(), PackageManager.GET_PERMISSIONS);
String[] permissionArray = packageInfo.requestedPermissions;
if (permissionArray != null) {
327
contents, and then rebuild them into a new APK file. Since the falsifier does not have the key of the
original developer, he would have to sign the new APK file with his own key. As the falsification of an
APK inevitably brings with a change in signature (certificate), it is possible to detect whether an APK
has been falsified at run time by comparing the certificate in the APK and the developer's certificate
embedded in the source code as below.
The following is a sample code. Also, a professional hacker will be able to easily circumvent the
detection of falsification if this implementation example is used as it is. Please apply this sample
code to your application by being aware that this is a simple implementation example.
Points:
1.
Verify that an application's certificate belongs to the developer before major processing is
started.
SignatureCheckActivity.java
package org.jssec.android.permission.signcheckactivity;
import org.jssec.android.shared.PkgCert;
import org.jssec.android.shared.Utils;
import android.app.Activity;
328
import android.content.Context;
import android.os.Bundle;
import android.widget.Toast;
public class SignatureCheckActivity extends Activity {
// Self signed certificate hash value
private static String sMyCertHash = null;
private static String myCertHash(Context context) {
if (sMyCertHash == null) {
if (Utils.isDebuggable(context)) {
// Certificate hash value of "androiddebugkey" of debug.
sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255";
} else {
// Certificate hash value of "my company key" of keystore
sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";
}
}
return sMyCertHash;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// *** POINT 1 *** Verify that an application's certificate belongs to the developer before major processing i
s started
if (!PkgCert.test(this, this.getPackageName(), myCertHash(this))) {
Toast.makeText(this, "Self-sign match NG", Toast.LENGTH_LONG).show();
finish();
return;
}
Toast.makeText(this, "Self-sign match OK", Toast.LENGTH_LONG).show();
}
}
PkgCert.java
package org.jssec.android.shared;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import
import
import
import
import
android.content.Context;
android.content.pm.PackageInfo;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
android.content.pm.Signature;
329
try {
PackageManager pm = ctx.getPackageManager();
PackageInfo pkginfo = pm.getPackageInfo(pkgname, PackageManager.GET_SIGNATURES);
if (pkginfo.signatures.length != 1) return null;
// Will not handle multiple signatures.
Signature sig = pkginfo.signatures[0];
byte[] cert = sig.toByteArray();
byte[] sha256 = computeSha256(cert);
return byte2hex(sha256);
} catch (NameNotFoundException e) {
return null;
}
}
private static byte[] computeSha256(byte[] data) {
try {
return MessageDigest.getInstance("SHA-256").digest(data);
} catch (NoSuchAlgorithmException e) {
return null;
}
}
private static String byte2hex(byte[] data) {
if (data == null) return null;
final StringBuilder hexadecimal = new StringBuilder();
for (final byte b : data) {
hexadecimal.append(String.format("%02X", b));
}
return hexadecimal.toString();
}
}
and features that are protected by Android OS. When the permission required is granted, the
permission is delegated to the application and the application would be able to access the
Depending on how the program is designed, the application to which has been delegated (granted)
the permission is able to acquire data that is protected with the permission. Furthermore, the
application can offer another application the protected data without enforcing the same permission.
This is nothing less than permission-less application to access data that is protected by permission.
This is virtually the same thing as re-delegating the permission, and this is referred to the Permission
Re-delegation Problem. Accordingly, the specification of the permission mechanism of Android only
is able to manage permission of direct access from an application to protected data.
A specific example is shown in Figure 5.2-8. The application in the center shows that an application
which has declared android.permission.READ_CONTACTS to use it reads contacts and then stores
them into its own database. The Permission Re-delegation Problem occurs when information that has
been stored is offered to another application without any restriction via Content Provider.
330
http://www.jssec.org/dl/android_securecoding_en.pdf
Android Device
Application that
has declared
uses-permission
Content Provider
Contacts
AAA
AAA
BBB
BBB
CCC
Application without
declaration of
uses-permission
CCC
AAA
BBB
CCC
same permission. If that number is being called without the verification of a user, then also there is
the Permission Re-delegation Problem.
There are cases where the secondary provision of another application with nearly-intact information
asset or functional asset acquired with the permission is needed. In those cases, the provider-side
application must demand the same permission for the provision in order to maintain the original
level of protection. Also, in the case of only providing a portion of information asset as well as
functional asset in a secondary fashion, an appropriate amount of protection is necessary in
accordance with the degree of damage that is incurred when a portion of that information or
functional asset is exploited. We can use protective measures such as demanding permission as
similar to the former, verifying user consent, and setting up restrictions for target applications by
using "4.1.1.1 Creating/Using Private Activities," or "4.1.1.4 Creating/Using In-house Activities" etc.
Such Permission Re-delegation Problem is not only limited to the issue of the Android permission.
For an Android application, it is generic that the application aquires necessary information/functions
from different applications, networks, and storage media. And in many cases, some permissions as
well as restrictions are needed to access them. For example, if the provider source is an Android
application, it is the permission, if it is a network, then it is the log-in, and if it is a storage media,
there will be access restrictions. Therefore, such measures need to be implemented for an
application after carefully considering as information/functions are not used in the contrary manner
of the user's intention. This is especially important at the time of providing aquired
storage media. Depending on the necessity, you have to enforce permission or restrict usage like the
Android permission. Asking for the user's consent is part of the solution.
331
In the following code, we demonstrate a case where an application that acquires a list from the
contact database by using READ_CONTACTS permission enforces the same READ_CONTACTS
permission on the information destination source.
Point
1.
AndroidManifest.xml
When an application enforces multiple permissions, the above method will not solve it. By using
Context#checkCallingPermission() or PackageManager#checkPermission() from the source code,
verify whether the invoker application has declared all permissions with uses-permission in the
Manifest.
332
}
finish();
}
333
name, password) which is necessary for applications to access to online service and authentication
token. A user needs to register the account information to Account Manager in advance, and when an
application tries to access to online service, Account Manager will automatically provide application
authentication token after getting user's permission. The advantage of Account Manager is that an
application doesn't need to handle the extremely sensitive information, password.
The structure of account management function which uses Account Manager is as per below Figure
5.3-1. "Requesting application" is the application which accesses the online service, by getting
authentication token, and this is above mentioned application. On the other hand, "Authenticator
application" is function extension of Account Manager, and by providing Account Manager of an
object called Authenticator, as a result Account Manager can manage centrally the account
information and authentication token of the online service.Requesting application and Authenticator
application don't need to be the separate ones, so these can be implemented as a single application.
Web server
Android device
User
Application
Get authentication
token, Add accounts
etc.
Android Framework
Authenticator application
Online service A
Account
Manager
Authenticator of
online service A
Account
management
function
Eace function
Of service
Authenticator application can be the different ones. However, only in Android 4.0.x devices, there's
an Android Framework bug, and when the signature key of user application and Authenticator
application are different, exception occurs in user application, and in-house account cannot be used.
The following sample code does not implement any workarounds against this defect. Please refer to
"5.3.3.2 Exception Occurs When Signature Keys of User Application and Authenticator Application
Are Different, in Android 4.0.x" for details.
5.3.1. Sample Code
"5.3.1.1 Creating In-house account" is prepared as a sample of Authenticator application, and
"5.3.1.2 Using In-house Account" is prepared as a sample of requesting application. In sample code
set which is distributed in JSSEC's Web site, each of them is corresponded to AccountManager
in-house account. There is no Activity which can be launched from home screen in this application.
Please pay attention that it's called indirectly via Account Manager from another sample code "5.3.1.2
3.
2.
4.
The explicit intent which the class name of the login screen activity is specified must be set to
5.
Sensitive information (like account information or authentication token) must not be output to
6.
7.
KEY_INTENT.
the log.
HTTPS should be used for communication between an authenticator and the online services.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.jssec.android.accountmanager.authenticator"
android:versionCode="1"
android:versionName="1.0" xmlns:tools="http://schemas.android.com/tools">
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17" />
<!-- Necessary Permission to implement Authenticator -->
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<application
android:allowBackup="false"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<!-- Service which gives IBinder of Authenticator to AccountManager -->
<!-- *** POINT 1 *** The service that provides an authenticator must be private. -->
<service
android:name=".AuthenticationService"
android:exported="false" >
<!-- intent-filter and meta-data are usual pattern. -->
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator" />
335
</service>
<!-- Activity for for login screen which is displayed when adding an account -->
<!-- *** POINT 2 *** The login screen activity must be implemented in an authenticator application. -->
<!-- *** POINT 3 *** The login screen activity must be made as a public activity. -->
<activity
android:name=".LoginActivity"
android:exported="true"
android:label="@string/login_activity_title"
android:theme="@android:style/Theme.Dialog"
tools:ignore="ExportedActivity" />
</application>
</manifest>
Define Authenticator by XML file. Specify account type etc of in-house account.
res/xml/authenticator.xml
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="org.jssec.android.accountmanager"
android:icon="@drawable/ic_launcher"
android:label="@string/label"
android:smallIcon="@drawable/ic_launcher" />
Service which gives Authenticator's Instance to AccountManager. Easy implementation which returns
AuthenticationService.java
package org.jssec.android.accountmanager.authenticator;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class AuthenticationService extends Service {
private JssecAuthenticator mAuthenticator;
@Override
public void onCreate() {
mAuthenticator = new JssecAuthenticator(this);
}
@Override
public IBinder onBind(Intent intent) {
return mAuthenticator.getIBinder();
}
}
336
AbstractAccountAuthenticator, and all abstract methods are implemented. These methods are called
by Account Manager. At addAccount() and at getAuthToken(), the intent for launching LoginActivity
to get authentication token from online service are returned to Account Manager.
JssecAuthenticator.java
package org.jssec.android.accountmanager.authenticator;
import
import
import
import
import
import
import
import
android.accounts.AbstractAccountAuthenticator;
android.accounts.Account;
android.accounts.AccountAuthenticatorResponse;
android.accounts.AccountManager;
android.accounts.NetworkErrorException;
android.content.Context;
android.content.Intent;
android.os.Bundle;
static
static
static
static
final
final
final
final
String
String
String
String
JSSEC_ACCOUNT_TYPE = "org.jssec.android.accountmanager";
JSSEC_AUTHTOKEN_TYPE = "webservice";
JSSEC_AUTHTOKEN_LABEL = "JSSEC Web Service";
RE_AUTH_NAME = "reauth_name";
337
338
This is Login activity which sends an account name and password to online service, and perform login
authentication, and as a result, get an authentication token. It's displayed when adding a new
account or when getting authentication token again. It's supposed that the actual access to online
package org.jssec.android.accountmanager.authenticator;
import org.jssec.android.accountmanager.webservice.WebService;
import
import
import
import
import
import
import
import
import
import
import
android.accounts.Account;
android.accounts.AccountAuthenticatorActivity;
android.accounts.AccountManager;
android.content.Intent;
android.os.Bundle;
android.text.InputType;
android.text.TextUtils;
android.util.Log;
android.view.View;
android.view.Window;
android.widget.EditText;
339
if (mReAuthName == null) {
// Register accounts which logged in successfully, to aAccountManager
// *** POINT 6 *** Password should not be saved in Account Manager.
AccountManager am = AccountManager.get(this);
Account account = new Account(name, JssecAuthenticator.JSSEC_ACCOUNT_TYPE);
am.addAccountExplicitly(account, null, null);
am.setAuthToken(account, JssecAuthenticator.JSSEC_AUTHTOKEN_TYPE, authToken);
Intent intent = new Intent();
intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, name);
intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE,
JssecAuthenticator.JSSEC_ACCOUNT_TYPE);
setAccountAuthenticatorResult(intent.getExtras());
setResult(RESULT_OK, intent);
} else {
// Return authentication token
Bundle bundle = new Bundle();
bundle.putString(AccountManager.KEY_ACCOUNT_NAME, name);
bundle.putString(AccountManager.KEY_ACCOUNT_TYPE,
JssecAuthenticator.JSSEC_ACCOUNT_TYPE);
bundle.putString(AccountManager.KEY_AUTHTOKEN, authToken);
setAccountAuthenticatorResult(bundle);
setResult(RESULT_OK);
}
finish();
}
}
Actually, WebService class is dummy implementation here, and this is the sample implementation
which supposes authentication is always successful, and fixed character string is returned as an
authentication token.
340
WebService.java
package org.jssec.android.accountmanager.webservice;
public class WebService {
/**
* Suppose to access to account managemnet function of online service.
*
* @param username Account name character string
* @param password password character string
* @return Return authentication token
*/
public String login(String username, String password) {
// *** POINT 7 *** HTTPS should be used for communication between an authenticator and the online services.
// Actually, communication process with servers is implemented here, but Omit here, since this is a sample.
return getAuthToken(username, password);
}
private String getAuthToken(String username, String password) {
// In fact, get the value which uniqueness and impossibility of speculation are guaranteed by the server,
// but the fixed value is returned without communication here, since this is sample.
return "c2f981bda5f34f90c0419e171f60f45c";
}
}
341
token. When another sample application "5.3.1.1 Creating In-house account" is installed in a device,
in-house account can be added or authentication token can be got. "Access request" screen is
displayed only when the signature keys of both applications are different.
Execute the account process after verifying if the authenticator is regular one.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.jssec.android.accountmanager.user"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<application
android:allowBackup="false"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".UserActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
342
</intent-filter>
</activity>
</application>
</manifest>
Activity of user application. When tapping the button on the screen, either addcount() or
getAuthToken() is to be executed. Authenticator which corresponds to the specific account type may
be fake in some cases, so pay attention that the account process is started after verifying that the
Authenticator is regular one.
UserActivity.java
package org.jssec.android.accountmanager.user;
import java.io.IOException;
import org.jssec.android.shared.PkgCert;
import org.jssec.android.shared.Utils;
import
import
import
import
import
import
import
import
import
import
import
import
android.accounts.Account;
android.accounts.AccountManager;
android.accounts.AccountManagerCallback;
android.accounts.AccountManagerFuture;
android.accounts.AuthenticatorDescription;
android.accounts.AuthenticatorException;
android.accounts.OperationCanceledException;
android.app.Activity;
android.content.Context;
android.os.Bundle;
android.view.View;
android.widget.TextView;
343
new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> future) {
try {
Bundle result = future.getResult();
String type = result.getString(AccountManager.KEY_ACCOUNT_TYPE);
String name = result.getString(AccountManager.KEY_ACCOUNT_NAME);
if (type != null && name != null) {
logLine("Add the following accounts:");
logLine(" Account type: %s", type);
logLine(" Account name: %s", name);
} else {
String code = result.getString(AccountManager.KEY_ERROR_CODE);
String msg = result.getString(AccountManager.KEY_ERROR_MESSAGE);
logLine("The account cannot be added");
logLine(" Error code %s: %s", code, msg);
}
} catch (OperationCanceledException e) {
} catch (AuthenticatorException e) {
} catch (IOException e) {
}
}
},
null);
}
public void getAuthToken(View view) {
logLine();
logLine("Get token");
// *** POINT 1 *** After checking that the Authenticator is the regular one, execute account process.
if (!checkAuthenticator()) return;
AccountManager am = AccountManager.get(this);
Account[] accounts = am.getAccountsByType(JSSEC_ACCOUNT_TYPE);
if (accounts.length > 0) {
Account account = accounts[0];
am.getAuthToken(account, JSSEC_TOKEN_TYPE, null, this,
new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> future) {
try {
Bundle result = future.getResult();
String name = result.getString(AccountManager.KEY_ACCOUNT_NAME);
String authtoken = result.getString(AccountManager.KEY_AUTHTOKEN);
logLine("%s-san's token:", name);
if (authtoken != null) {
logLine("
%s", authtoken);
} else {
logLine("
Couldn't get");
}
} catch (OperationCanceledException e) {
logLine(" Exception: %s",e.getClass().getName());
} catch (AuthenticatorException e) {
logLine(" Exception: %s",e.getClass().getName());
} catch (IOException e) {
logLine(" Exception: %s",e.getClass().getName());
}
}
}, null);
344
} else {
logLine("Account is not registered.");
}
}
// *** POINT 1 *** Verify that Authenticator is regular one.
private boolean checkAuthenticator() {
AccountManager am = AccountManager.get(this);
String pkgname = null;
for (AuthenticatorDescription ad : am.getAuthenticatorTypes()) {
if (JSSEC_ACCOUNT_TYPE.equals(ad.type)) {
pkgname = ad.packageName;
break;
}
}
if (pkgname == null) {
logLine("Authenticator cannot be found.");
return false;
}
logLine(" Account type: %s", JSSEC_ACCOUNT_TYPE);
logLine(" Package name of Authenticator: ");
logLine("
%s", pkgname);
if (!PkgCert.test(this, pkgname, getTrustedCertificateHash(this))) {
logLine(" It's not regular Authenticator(certificate is not matched.)");
return false;
}
logLine(" This is regular Authenticator.");
return true;
}
// Certificate hash value of regular Authenticator application
// Certificate hash value can be checked in sample applciation JSSEC CertHash Checker
private String getTrustedCertificateHash(Context context) {
if (Utils.isDebuggable(context)) {
// Certificate hash value of debug.keystore "androiddebugkey"
return "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255";
} else {
// Certificate hash value of keystore "my company key"
return "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";
}
}
private void log(String str) {
mLogView.append(str);
}
private void logLine(String line) {
log(line + "n");
}
private void logLine(String fmt, Object... args) {
logLine(String.format(fmt, args));
}
private void logLine() {
log("n");
345
}
}
PkgCert.java
package org.jssec.android.shared;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import
import
import
import
import
android.content.Context;
android.content.pm.PackageInfo;
android.content.pm.PackageManager;
android.content.pm.PackageManager.NameNotFoundException;
android.content.pm.Signature;
346
2.
3.
(Required)
(Required)
Other Applications
(Required)
The Login Screen Activity Must Be Made as a Public Activity and Suppose Attack Accesses by
4.
Provide KEY_INTENT with Explicit Intent with the Specified Class Name of Login Screen Activity
5.
Sensitive Information (like Account Information and Authentication Token) Must Not Be Output to
6.
7.
(Required)
the Log
(Required)
(Recommended)
HTTPS Should Be Used for Communication Between an Authenticator and the Online Service
(Required)
Account Process Should Be Executed after verifying if the Authenticator is the regular one
(Required)
(Required)
It's presupposed that the Service which provides with Authenticator is used by Account Manager, and
it should not be accessed by other applications. So, by making it Private Service, it can exclude
accesses by other applications. In addition, Account Manager runs with system privilege, so Account
(Required)
Login screen which is displayed when adding new account and getting authentication token again,
should be implemented by Authenticator application. Own Login screen should not be prepared in
user application side. As mentioned at the beginning of this article, [The advantage of
AccountManager is that the extremely sensitive information/password is not necessarily to be
handled by application.], If login screen is prepared in user application side, password is handled by
user application, and its design becomes what is beyond the policy of Account Manager.
By preparing login screen by Authenticator application, who can operate login screen is limited only
the device's user. It means that there's no way to attack the account for malicious applications by
attempting to login directly, or by creating an account.
347
5.3.2.3. The Login Screen Activity Must Be Made as a Public Activity and Suppose Attack Accesses by
Other Applications
(Required)
Login screen Activity is the system launched by the user application's priviledge. In order that the
login screen Activity is displayed even when the signature keys of user application and Authenticator
application are different, login screen Activity should be implemented as Public Activity.
What login screen Activity is public Activity means, that there's a chance that it may be launched by
malicious applications. Never trust on any input data. Hence, it's necessary to take the
5.3.2.4. Provide KEY_INTENT with Explicit Intent with the Specified Class Name of Login Screen Activity
(Required)
When Authenticator needs to open login screen Activity, Intent which launches login screen Activity is
to be given in the Bundle that is returned to Account Manager, by KEY_INTENT. The Intent to be given,
should be the explicit Intent which specifies class name of login screen Activity. In the case of
specifying implicit Intent which specifies Action name, there's a possibility that not login screen
Activity which Authenticator application prepared by itself, but an Activity which other application
prepared, is launched. When a malicious application prepares a login screen which looks like the
regular login screen, there's a risk that a user may input password in the fake login screen.
5.3.2.5. Sensitive Information (like Account Information and Authentication Token) Must Not Be
Output to the Log
(Required)
Applications which access to online service sometimes face a trouble like it cannot access to online
service successfully. The causes of unsuccessful access are various, like lack in network environment
error, etc. A common implementation is that a program outputs the detailed information to log, so
that developer can analyze the cause of a problem later.
Sensitive information like password or authentication token should not be output to log. Log
information can be read from other applications, so it may become the cause of information leakage.
Also, account names should not be output to log, if it could be lead the damage of leakage.
(Recommended)
Two of authentication information, password and authentication token, can be saved in an account to
be register to AccountManager. This information is to be saved in /data/system/accounts.db, in a
plain text (i.e. without encryption). To read in the contents of accounts.db, either root privilege or
system privilege is required, and it cannot be read from the marketed Android devices. In the case
there is any vulnerability in Android OS, which root privilege or system privilege may be taken over by
attackers, authentication information which is saved in accounts.db will be on the edge of the risk.
348
The Authentication application which is introduced in this article, is designed to save authentication
token in AccountManager without saving user password. When accessing to online service
continuously in a certain period, generally the expiration period of authentication token is extended,
In general, valid date of authentication token is shorter than password, and it's characteristic that it
can be disabled anytime. In case, authentication token is leaked, it can be disabled, so authentication
token is comparatively safer, compared with password. In the case authentication token is disabled,
user can input the password again to get a new authentication token.
If disabling password when it's leaked, user cannot use online service any more. In this case, it
requires call center support etc, and it will take huge cost. Hence, it's better to avoid from the design
to save password in AccountManager. In case, the design to save password cannot be avoided, high
level of reverse engineering counter-measures like encrypting password and obfuscating the key of
that encryption, should be taken.
5.3.2.7. HTTPS Should Be Used for Communication Between an Authenticator and the Online Service
(Required)
Password or authentication token is so called authentication information, and if it's taken over by the
third party, the third party can masquerade as the valid user. Since Authenticator sends/receives
these types of authentication information with online service, reliable encrypted communication
method like an HTTPS should be used.
5.3.2.8. Account Process Should Be Executed after verifying if the Authenticator is the regular one
(Required)
In the case there are several Authenticators which the same account type is defined in a device,
Authenticator which was installed earlier becomes valid. So, when the own Authenticator was
installed later, it's not to be used.
If the Authenticator which was installed earlier, is the malware's masquerade, account information
inputted by user may be taken over by malware. User application should verify the account type
which performs account operation, whether the regular Authenticator is allocated to it or not, before
Whether the Authenticator which is allocated to one account type is regular one or not, can be
verified by checking whether the certificate hash value of the package of Authenticator matches with
pre-confirmed valid certificate hash value. If the certificate hash values are found to be not matched,
a measure to prompt user to uninstall the package which includes the unexpected Authenticator
349
http://www.jssec.org/dl/android_securecoding_en.pdf
Method
Explanation
AUTHENTICATE_ACCOUNTS
getPassword()
To get password
getUserData()
addAccountExplicitly()
To add accounts to DB
peekAuthToken()
setAuthToken()
setPassword()
To change password
setUserData()
getAccounts()
getAccountsByType()
GET_ACCOUNTS
addOnAccountsUpdatedListener()
hasFeatures()
Whether
it
has
the
specified
function or not
MANAGE_ACCOUNTS
getAuthTokenByFeatures()
which
have
the
specified function
addAccount()
removeAccount()
To remove an account
clearPassword()
Initialize password
updateCredentials()
Request
user
to
change
password
editProperties()
VerifyCredentials()
USE_CREDENTIALS
MANAGE_ACCOUNTS
350
getAuthToken()
blockingGetAuthToken()
invalidateAuthToken()
or
USE_CREDENTIALS
restriction related to package signature key along with Permission. Specifically, the key for signature
of package that provides Authenticator and the key for signature of package in the application that
uses methods, should be the same. So, when distributing an application which uses method group
which AUTHENTICATE_ACCOUNTS Permission is necessary other than Authenticator, signature
In a development phase by Eclipse, since a fixed debug keystore might be shared by some eclipse
projects, developers might implement and test Acount Manager by considering only permissions and
no signature. It's necessary for especially developers who use the different signature keys per
applications, to be very careful when selecting which key to use for applications, considering this
restriction. In addition, since the data which is obtained by AccountManager includes the sensitive
information, so need to handle with care in order to decrease the risk like leakage or unauthorized
use.
5.3.3.2. Exception Occurs When Signature Keys of User Application and Authenticator Application Are
Different, in Android 4.0.x
When authentication token acquisition function, is required by the user application which is signed
by the developer key which is different from the signature key of Authenticator application that
includes Authenticator, AccountManager verifies users whether to grant the usage of authentication
token
or
not,
by
displaying
the
authentication
token
license
screen
as soon as this screen in opened by AccountManager, exception occurs, and application is force
details of the bug. This bug cannot be found in Android 2.3.x and earlier, and Android 4.1.x. and
later.
351
Android 4.0.x
Android 4.1.x
Figure 5.3-3 When displaying Android standard authentication token license screen.
352
http://www.jssec.org/dl/android_securecoding_en.pdf
view, HTTPS communication is preferable. Lately, major Web services like Google or Facebook have
been coming to use HTTPS as default.
In 2012, many defects in implementation of HTTPS communication were pointed out in Android
applications. These defects might have been implemented for accessing
operated by server certificates that are not issued by trusted third party certificate authorities, but
issued privately (hereinafter, called private certificates).
In this section, communication methods of HTTP and HTTPS are explained and the method to access
safely with HTTPS to a Web server operated by a private certificate is also described.
5.4.1. Sample Code
You can find out which type of HTTP/HTTPS communication you are supposed to implement through
the following chart (Figure 5.4-1) shown below.
Start
Send/Receive
the sensitive information?
Yes
No
Yes
No
No
Yes
Communicate by HTTP
Communicate by HTTPS
Communicate by HTTPS
with private certificate
353
http://www.jssec.org/dl/android_securecoding_en.pdf
Information for keeping authentication state (session ID, token, Cookie etc.)
A smartphone application with network communication is a part of "system" as well as a Web server.
And you have to select HTTP or HTTPS for each communication based on secure design and coding
considering the whole "system". Table 5.4-1 is for a comparison between HTTP and HTTPS. And
Table 5.4-2 is for the differences in sample codes.
Table 5.4-1 Comparison between HTTP communication method and HTTPS communication method
Characteristics
URL
Encrypting contents
Authenticating a server
Damage
Risk
HTTP
HTTPS
Not available
Available
High
Low
High
Low
High
Low
Communi-
Sending/Receiving
Server certificate
Communicating
HTTP
Not applicable
OK
via HTTP
Communicating
via HTTPS
Communicating
via
HTTPS
with
private certificate
cation
HTTPS
HTTPS
sensitive information
OK
party's
certificate
authorities
like
Private certificate
There are two major APIs for HTTP/HTTPS communications supported by Android. In this article and
sample codes, Apache HttpClient library is used because applications can control communication
way in detail.
354
prepared by attackers. HTTP communication can be used only if no damage is caused or the damage
is within the permissible extent even under the premises. If an application cannot accept the
premises, please refer to "5.4.1.2 Communicating via HTTPS" and "5.4.1.3 Communicating via HTTPS
with private certificate."
The following sample code shows an application which performs an image search on a Web server,
gets the result image and shows it. HTTP communication with the server is performed twice a search.
The first communication is for searching image data and the second is for getting it. The worker
thread for communication process using AsyncTask is created to avoid the communications
performing on the UI thread. Contents sent/received in the communications with the server are not
considered as sensitive (e.g. the character string for searching, the URL of the image, or the image
data) here. So, the received data such as the URL of the image and the image data may be provided by
attackers. To show the sample code simply, any countermeasures are not taken in the sample code
by considering the received attacking data as tolerable. Also, the handlings for possible exceptions
during JSON purse or showing image data are omitted. It is necessary to handle the exceptions
properly depending on the application specs.
Points:
1.
2.
HttpImageSearch.java
package org.jssec.android.https.imagesearch;
import
import
import
import
import
import
import
org.apache.http.HttpException;
org.apache.http.HttpResponse;
org.apache.http.HttpStatus;
org.apache.http.client.methods.HttpGet;
org.apache.http.impl.client.DefaultHttpClient;
org.apache.http.util.EntityUtils;
org.json.JSONObject;
import android.net.Uri;
import android.os.AsyncTask;
public abstract class HttpImageSearch extends AsyncTask<String, Void, Object> {
@Override
protected Object doInBackground(String... params) {
// Since use HttpClient for 2 times of GET requests, it shutdown at finally block.
DefaultHttpClient client = new DefaultHttpClient();
try {
// -------------------------------------------------------// Communication 1st time: Execute image search
// --------------------------------------------------------
355
// *** POINT 1 *** Sensitive information must not be contained in send data.
// Send image search character string
String search_url = Uri
.parse("http://ajax.googleapis.com/ajax/services/search/images?v=1.0")
.buildUpon()
.appendQueryParameter("q", params[0])
.build().toString();
HttpGet request = new HttpGet(search_url);
HttpResponse response = client.execute(request);
checkResponse(response);
// *** POINT 2 *** Suppose that received data may be sent from attackers.
// This is sample, so omit the process in case of the searching result is the data from an attacker.
// This is sample, so omit the exception process in case of JSON purse.
String result_json = EntityUtils.toString(response.getEntity(), "UTF-8");
String image_url = new JSONObject(result_json).getJSONObject("responseData")
.getJSONArray("results").getJSONObject(0).getString("url");
// -------------------------------------------------------// Communication 2nd time: Get images
// -------------------------------------------------------// *** POINT 1 *** Sensitive information must not be contained in send data.
request = new HttpGet(image_url);
response = client.execute(request);
checkResponse(response);
// *** POINT 2 *** Suppose that received data may be sent from attackers.
return EntityUtils.toByteArray(response.getEntity());
} catch(Exception e) {
return e;
} finally {
// Shutdown HttpClient without fail
client.getConnectionManager().shutdown();
}
}
private void checkResponse(HttpResponse response) throws HttpException {
int statusCode = response.getStatusLine().getStatusCode();
if (HttpStatus.SC_OK != statusCode) {
throw new HttpException("HttpStatus: " + statusCode);
}
}
}
ImageSearchActivity.java
package org.jssec.android.https.imagesearch;
import
import
import
import
import
import
import
import
import
356
android.app.Activity;
android.graphics.Bitmap;
android.graphics.BitmapFactory;
android.os.AsyncTask;
android.os.Bundle;
android.view.View;
android.widget.EditText;
android.widget.ImageView;
android.widget.TextView;
EditText mQueryBox;
TextView mMsgBox;
ImageView mImgBox;
AsyncTask<String, Void, Object> mAsyncTask ;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mQueryBox = (EditText)findViewById(R.id.querybox);
mMsgBox = (TextView)findViewById(R.id.msgbox);
mImgBox = (ImageView)findViewById(R.id.imageview);
}
@Override
protected void onPause() {
// After this, Activity may be deleted, so cancel the asynchronization process in advance.
if (mAsyncTask != null) mAsyncTask.cancel(true);
super.onPause();
}
public void onHttpSearchClick(View view) {
String query = mQueryBox.getText().toString();
mMsgBox.setText("HTTP:" + query);
mImgBox.setImageBitmap(null);
// Cancel, since the last asynchronous process might not have been finished yet.
if (mAsyncTask != null) mAsyncTask.cancel(true);
// Since cannot communicate by UI thread, communicate by worker thread by AsynchTask.
mAsyncTask = new HttpImageSearch() {
@Override
protected void onPostExecute(Object result) {
// Process the communication result by UI thread.
if (result instanceof Exception) {
Exception e = (Exception)result;
mMsgBox.append("nException occursn" + e.toString());
} else {
// Exception process when image display is omitted here, since it's sample.
byte[] data = (byte[])result;
Bitmap bmp = BitmapFactory.decodeByteArray(data, 0, data.length);
mImgBox.setImageBitmap(bmp);
}
}
}.execute(query); // pass search character string and start asynchronous process
}
public void onHttpsSearchClick(View view) {
String query = mQueryBox.getText().toString();
mMsgBox.setText("HTTPS:" + query);
mImgBox.setImageBitmap(null);
// Cancel, since the last asynchronous process might not have been finished yet.
if (mAsyncTask != null) mAsyncTask.cancel(true);
// Since cannot communicate by UI thread, communicate by worker thread by AsynchTask.
357
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.jssec.android.https.imagesearch"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="8" />
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:name=".ImageSearchActivity"
android:label="@string/app_name"
android:theme="@android:style/Theme.Light" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
358
connected server is trusted or not. To authenticate the server, Android HTTPS library verifies "server
certificate" which is transmitted from the server in the handshake phase of HTTPS transaction with
following points:
CN in Subject of the server certificate equals to the host name of the serve.
When an error is encountered during the verification above, a server certificate verification exception
(SSLException) is thrown. The error occurs due to any defects in the server certificate or
man-in-the-middle attacks by attackers. You have to handle the exception with an appropriate
sequence based on the application specifications.
The next a sample code is for HTTPS communication which connects to a Web server with a server
certificate issued by a trusted third party certificate authority. For HTTPS communication with a
server certificate issued privately, please refer to "5.4.1.3 Communicating via HTTPS with private
certificate."
The following sample code shows an application which performs an image search on a Web server,
gets the result image and shows it. HTTPS communication with the server is performed twice a search.
The first communication is for searching image data and the second is for getting it. The worker
thread for communication process using AsyncTask is created to avoid the communications
performing on the UI thread. All contents sent/received in the communications with the server are
considered as sensitive (e.g. the character string for searching, the URL of the image, or the image
data) here. To show the sample code simply, no special handling for SSLException is performed. It is
necessary to handle the exceptions properly depending on the application specifications.
Points:
1.
2.
3.
4.
HttpsImageSearch.java
package org.jssec.android.https.imagesearch;
import javax.net.ssl.SSLException;
import
import
import
import
import
import
org.apache.http.HttpException;
org.apache.http.HttpResponse;
org.apache.http.HttpStatus;
org.apache.http.client.methods.HttpGet;
org.apache.http.impl.client.DefaultHttpClient;
org.apache.http.util.EntityUtils;
359
import org.json.JSONObject;
import android.net.Uri;
import android.os.AsyncTask;
public abstract class HttpsImageSearch extends AsyncTask<String, Void, Object> {
@Override
protected Object doInBackground(String... params) {
// Since HttpClient is used for 2 times of GET requests,it shutdown at finally block.
DefaultHttpClient client = new DefaultHttpClient();
try {
// -------------------------------------------------------// Communication 1st time : Execute image search
// -------------------------------------------------------// *** POINT 1 *** URI starts with https://.
// *** POINT 2 *** Sensitive information may be contained in send data.
String search_url = Uri
.parse("https://ajax.googleapis.com/ajax/services/search/images?v=1.0")
.buildUpon()
.appendQueryParameter("q", params[0])
.build().toString();
HttpGet request = new HttpGet(search_url);
HttpResponse response = client.execute(request);
checkResponse(response);
// *** POINT 3 *** Received data can be trusted as same as the server.
String result_json = EntityUtils.toString(response.getEntity(), "UTF-8");
String image_url = new JSONObject(result_json).getJSONObject("responseData")
.getJSONArray("results").getJSONObject(0).getString("url");
// -------------------------------------------------------// Communication 2nd time : Get image
// -------------------------------------------------------// *** POINT 1 *** URI starts with https://.
// *** POINT 2 *** Sensitive information may be contained in send data.
request = new HttpGet(image_url);
response = client.execute(request);
checkResponse(response);
// *** POINT 3 *** Received data can be trusted as same as the server.
return EntityUtils.toByteArray(response.getEntity());
} catch(SSLException e) {
// *** POINT 4 *** SSLException should be handled with an appropriate sequence in an application.
// Omit exception process, since it's sample
return e;
} catch(Exception e) {
return e;
} finally {
// Shutdown HttpClient without fail.
client.getConnectionManager().shutdown();
}
}
private void checkResponse(HttpResponse response) throws HttpException {
int statusCode = response.getStatusLine().getStatusCode();
360
if (HttpStatus.SC_OK != statusCode) {
throw new HttpException("HttpStatus: " + statusCode);
}
}
}
Other sample code files are the same as "5.4.1.1 Communicating via HTTP," so please refer to
"5.4.1.1 Communicating via HTTP."
361
private certificate authority and private certificates and setting HTTPS settings in a Web server. The
sample program has a cacert.crt file in assets. It is a root certificate file of private certificate
authority.
The following sample code shows an application which gets an image on a Web server and shows it.
HTTPS is used for the communication with the server. The worker thread for communication process
using AsyncTask is created to avoid the communications performing on the UI thread. All contents
(the URL of the image and the image data) sent/received in the communications with the server are
considered as sensitive here. To show the sample code simply, no special handling for SSLException
Verify a server certificate with the root certificate of a private certificate authority.
2.
3.
4.
5.
PrivateCertificathettpsGet.java
package org.jssec.android.https.privatecertificate;
import java.security.KeyStore;
import javax.net.ssl.SSLException;
import
import
import
import
import
import
import
import
org.apache.http.HttpException;
org.apache.http.HttpResponse;
org.apache.http.HttpStatus;
org.apache.http.client.methods.HttpGet;
org.apache.http.conn.scheme.Scheme;
org.apache.http.conn.ssl.SSLSocketFactory;
org.apache.http.impl.client.DefaultHttpClient;
org.apache.http.util.EntityUtils;
import android.content.Context;
import android.os.AsyncTask;
public abstract class PrivateCertificateHttpsGet extends AsyncTask<String, Void, Object> {
private Context mContext;
public PrivateCertificateHttpsGet(Context context) {
mContext = context;
}
362
@Override
protected Object doInBackground(String... params) {
DefaultHttpClient client = new DefaultHttpClient();
try {
// *** POINT 1 *** Verify a server certificate with the root certificate of a private certificate authorit
y.
// Set keystore which includes only private certificate that is stored in assets, to client.
KeyStore ks = KeyStoreUtil.getEmptyKeyStore();
KeyStoreUtil.loadX509Certificate(ks,
mContext.getResources().getAssets().open("cacert.crt"));
Scheme sch = new Scheme("https", new SSLSocketFactory(ks), 443);
client.getConnectionManager().getSchemeRegistry().register(sch);
// *** POINT 2 *** URI starts with https://.
// *** POINT 3 *** Sensitive information may be contained in send data.
HttpGet request = new HttpGet(params[0]);
HttpResponse response = client.execute(request);
checkResponse(response);
// *** POINT 4 *** Received data can be trusted as same as the server.
return EntityUtils.toByteArray(response.getEntity());
} catch(SSLException e) {
// *** POINT 5 *** SSLException should be handled with an appropriate sequence in an application.
// Exception process is omitted here since it's sample.
return e;
} catch(Exception e) {
return e;
} finally {
// Shutdown HttpClient without fail.
client.getConnectionManager().shutdown();
}
}
private void checkResponse(HttpResponse response) throws HttpException {
int statusCode = response.getStatusLine().getStatusCode();
if (HttpStatus.SC_OK != statusCode) {
throw new HttpException("HttpStatus: " + statusCode);
}
}
}
KeyStoreUtil.java
package org.jssec.android.https.privatecertificate;
import
import
import
import
import
import
import
import
import
import
java.io.IOException;
java.io.InputStream;
java.security.KeyStore;
java.security.KeyStoreException;
java.security.NoSuchAlgorithmException;
java.security.cert.Certificate;
java.security.cert.CertificateException;
java.security.cert.CertificateFactory;
java.security.cert.X509Certificate;
java.util.Enumeration;
363
PrivateCertificathettpsActivity.java
package org.jssec.android.https.privatecertificate;
import
import
import
import
import
import
import
import
import
android.app.Activity;
android.graphics.Bitmap;
android.graphics.BitmapFactory;
android.os.AsyncTask;
android.os.Bundle;
android.view.View;
android.widget.EditText;
android.widget.ImageView;
android.widget.TextView;
EditText mUrlBox;
TextView mMsgBox;
ImageView mImgBox;
AsyncTask<String, Void, Object> mAsyncTask ;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
364
mUrlBox = (EditText)findViewById(R.id.urlbox);
mMsgBox = (TextView)findViewById(R.id.msgbox);
mImgBox = (ImageView)findViewById(R.id.imageview);
}
@Override
protected void onPause() {
// After this, Activity may be discarded, so cancel asynchronous process in advance.
if (mAsyncTask != null) mAsyncTask.cancel(true);
super.onPause();
}
public void onClick(View view) {
String url = mUrlBox.getText().toString();
mMsgBox.setText(url);
mImgBox.setImageBitmap(null);
// Cancel, since the last asynchronous process might have not been finished yet.
if (mAsyncTask != null) mAsyncTask.cancel(true);
// Since cannot communicate through UI thread, communicate by worker thread by AsynchTask.
mAsyncTask = new PrivateCertificateHttpsGet(this) {
@Override
protected void onPostExecute(Object result) {
// Process the communication result through UI thread.
if (result instanceof Exception) {
Exception e = (Exception)result;
mMsgBox.append("nException occursn" + e.toString());
} else {
byte[] data = (byte[])result;
Bitmap bmp = BitmapFactory.decodeByteArray(data, 0, data.length);
mImgBox.setImageBitmap(bmp);
}
}
}.execute(url); // Pass URL and start asynchronization process
}
}
365
(Required)
2.
4.
TrustManager Must Not Be Changed and Custom TrustManager Must Not Be Created
3.
5.
(Required)
(Required)
(Required)
HostnameVerifier Must Not Be Changed and Custom HostnameVerifier Must Not Be Created
(Required)
(Required)
In HTTP transaction, sent and received information might be sniffed or tampered and the connected
server might be masqueraded. Sensitive information must be sent/ received by HTTPS
communication.
5.4.2.2. Received Data over HTTP Must be Handled Carefully and Securely
(Required)
Received data in HTTP communications might be generated by attackers for exploiting vulnerability
of an application. So you have to suppose that the application receives any values and formats of data
and then carefully implement data handlings for processing received data so as not to put any
vulnerabilities in. Please refer to "3.2 Handling Input Data Carefully and Securely"
5.4.2.3. SSLException Must Be Handled Appropriately like Notification to User
(Required)
In HTTPS communication, SSLException occurs as a verification error when a server certificate is not
valid or the communication is under the man-in-the-middle attack. So you have to implement an
appropriate exception handling for SSLException. Notifying the user of the communication failure,
logging the failure and so on can be considered as typical implementations of exception handling.
On the other hand, no special notice to the user might be required in some case. Like this, because
how to handle SSLException depends on the application specs and characteristics you need to
determine it after first considering thoroughly.
SSLException occurs, so it must not be implemented like trying to send/receive sensitive information
again via non secure protocol such as HTTP.
5.4.2.4. TrustManager Must Not Be Changed and Custom TrustManager Must Not Be Created
(Required)
Just Changing KeyStore which is used for verifying server certificates is enough to communicate via
HTTPS with a private certificate like self-signed certificate. However, as explained in "5.4.3.3 Risky
366
Code that Disables Certificate Verification," there are so many dangerous TrustManager
implementations as sample codes for such purpose on the Internet. An Application implemented by
referring to these sample codes may have the vulnerability.
When you need to communicate via HTTPS with a private certificate, refer to the secure sample code
in "5.4.1.3 Communicating via HTTPS with private certificate."
Of course, custom TrustManager can be implemented securely, but enough knowledge for
encryption processing and encryption communication is required so as not to implement vulnerable
5.4.2.5. HostnameVerifier Must Not Be Changed and Custom HostnameVerifier Must Not Be Created
(Required)
Just Changing KeyStore which is used for verifying server certificates is enough to communicate via
HTTPS with a private certificate like self-signed certificate. However, as explained in "5.4.3.3 Risky
Code that Disables Certificate Verification," there are so many dangerous HostnameVerifier
implementations as sample codes for such purpose on the Internet. An Application implemented by
referring to these sample codes may have the vulnerability.
When you need to communicate via HTTPS with a private certificate, refer to the secure sample code
in "5.4.1.3 Communicating via HTTPS with private certificate."
Of course, custom HostnameVerifier can be implemented securely, but enough knowledge for
encryption processing and encryption communication is required so as not to implement vulnerable
367
In this section, how to create a private certificate and configure server settings in Linux such as
Ubuntu and CentOS is described. Private certificate means a server certificate which is issued
privately and is told from server certificates issued by trusted third party certificate authorities like
Cybertrust and VeriSign.
certificate. You can issue plural private certificates by using the single private certificate
authority. PC in which the private certificate authority is stored should be limited strictly to be
accessed just by trusted persons.
To create a private certificate authority, you have to create two files such as the following shell
script newca.sh and the setting file openssl.cnf and then execute them. In the shell script,
CASTART and CAEND stand for the valid period of certificate authority and CASUBJ stands for the
name of certificate authority. So these values need to be changed according to a certificate
authority you create. While executing the shell script, the password for accessing the certificate
authority is asked for 3 times in total, so you need to input it every time.
newca.sh Shell Script to create certificate authority
#!/bin/bash
umask 0077
CONFIG=openssl.cnf
CATOP=./CA
CAKEY=cakey.pem
CAREQ=careq.pem
CACERT=cacert.pem
CAX509=cacert.crt
CASTART=130101000000Z # 2013/01/01 00:00:00 GMT
CAEND=230101000000Z
# 2023/01/01 00:00:00 GMT
CASUBJ="/CN=JSSEC Private CA/O=JSSEC/ST=Tokyo/C=JP"
mkdir
mkdir
mkdir
mkdir
mkdir
touch
-p ${CATOP}
-p ${CATOP}/certs
-p ${CATOP}/crl
-p ${CATOP}/newcerts
-p ${CATOP}/private
${CATOP}/index.txt
368
openssl.cnf - Setting file of openssl command which 2 shell scripts refers in common.
[ ca ]
default_ca
= CA_default
[ CA_default ]
dir
= ./CA
certs
= $dir/certs
crl_dir
= $dir/crl
database
= $dir/index.txt
#Proprietary-defined _subject = no
= match
= match
= supplied
= optional
= supplied
= optional
[ usr_cert ]
basicConstraints=CA:FALSE
nsComment
= "OpenSSL Generated Certificate"
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
[ v3_ca ]
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer
basicConstraints = CA:true
After executing the above shall script, a directory named CA is created just under the work
directory. This CA directory is just a private certificate authority. CA/cacert.crt file is the root
certificate of the private certificate authority. And it's stored in assets directory of an application
as described in "5.4.1.3 Communicating via HTTPS with private certificate," or it's installed in
Android device as described in "5.4.3.2 Install Root Certificate of Private Certificate Authority to
Android OS's Certification Store (Android 4.0 and later)."
369
execute it. In the shell script, SVSTART and SVEND stand for the valid period of private certificate,
and SVSUBJ stands for the name of Web server, so these values need to be changed according to
the target Web server. Especially, you need to make sure not to set a wrong host name to /CN of
SVSUBJ with which the host name of Web server is to be specified. While executing the shell script,
the password for accessing the certificate authority is asked, so you need to input the password
which you have set when creating the private certificate authority. After that, y/n is asked 2 times
in total and you need to input y every time.
After executing the above shell script, both svkey.pem (private key file) and svcert.pem (private
certificate file) for Web server are generated just under work directory.
When Web server is Apache, you will specify prikey.pem and cert.pem in the confituration file as
follows.
SSLCertificateFile
"/path/to/svcert.pem"
SSLCertificateKeyFile "/path/to/svkey.pem"
370
http://www.jssec.org/dl/android_securecoding_en.pdf
5.4.3.2. Install Root Certificate of Private Certificate Authority to Android OS's Certification Store
(Android 4.0 and later)
In the sample code of "5.4.1.3 Communicating via HTTPS with private certificate," the method to
establish HTTPS sessions to a Web server from one application using a private certificate by installing
the root certificate into the application is introduced. In this section, the method to establish HTTPS
sessions to Web servers from all applications using private certificates by installing the root
certificate into Android OS is to be introduced. Note that all you install should be certificates issued
by trusted certificate authorities including your own certificate authorities. This step can be applied
to just devices with Android 4.0 or later.
First of all, you need to copy the root certificate file "cacert.crt" to the internal storage of an Android
device.
You
can
also
get
the
root
https://selfsigned.jssec.org/cacert.crt.
certificate
file
used
in
the
sample
code
from
And then, you will open Security page from Android Settings and you can install the root certificate in
an Android device by doing as follows.
371
372
Server certificate
validation error occurs
Possible to communicate
securely and correctry
https://selfsigned.jssec.org/droid_knight.png
Figure 5.4-4 Once root certificate installed, private certificates can be verified correctly.
By installing the root certificate this way, even applications using the sample code "5.4.1.2
Communicating via HTTPS" can correctly connect via HTTPS to a Web server which is operated with a
private certificate.
373
HTTPS with Web servers even after certificate verification errors occur, are found on the Internet.
Since they are introduced as the way to communicate via HTTPS with a Web server using a private
certificate, there have been so many applications created by developers who have used those sample
codes by copy and paste. Unfortunately, most of them are vulnerable to man-in-the-middle attack.
As mentioned in the top of this article, "In 2012, many defects in implementation of HTTPS
communication were pointed out in Android applications", many Android applications which would
Several code snipets to cause vulnerable HTTPS communication are shown below. When you find this
type of code snipets, it's highly recommended to replace the sample code of "5.4.1.3 Communicating
via HTTPS with private certificate."
@Override
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
// Do nothing -> accept any certificates
}
@Override
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
// Do nothing -> accept any certificates
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
SSLSocketFactory sf;
...
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
374
6. Difficult Problems
In Android, there are some problems that it is difficult to assure a security by application
being abused by the malicious third party or used by users carelessly, these functions are always
holding risks that may lead to security problems like information leakage. In this chapter, by
indicating risk mitigation plans that developers can take against these functions, some topics that
Copy & paste are the functions which users often use in a casual manner. For example, not a few
users use these functions to store curious information or important information to remember in a
mail or a web page into a notepad, or to copy and to paste a password from a notepad in which
passwords are stored in order not to forget in advance. These are very casual actions at a glance, but
actually there's a hidden risk that user handling information may be stolen.
The risk is related to mechanism of copy & paste in Android system. The information which was
copied by user or application, is once stored in the buffer called Clipboard. The information stored in
a risk which leads to information leakage in this Clipboard function. It is because the entity of
Clipboard is single in a system and any application can obtain the information stored in Clipboard at
any time by using ClipboardManager. It means that all the information which user copied/cut, is
leaked out to the malicious application.
Hence, application developers need to take measures to minimize the possibility of information
Roughly speaking, there are two outlooks of counter-measures to mitigate the risk of information
leakage form Clipboard.
1.
2.
Firstly, let us discuss the countermeasure 1 above. Supposing that a user copies character strings
from other applications like note pad, Web browser or mailer application, and then paste it to
EditText in your application. As it turns out, there's no basic counter-measure to prevent from
sensitive information leakage due to copy & paste, in this scenario. Since there's no function in
Android to control copy operations by the third party application.
So, regarding the countermeasure 1, there's no method other than explaining users the risk of
copying & pasting sensitive information, and just continuing to enlighten users to decrease the
6.1 Risk of Information Leakage from Clipboard
375
leakage is to prohibit copying/cutting operations from View (TextView, EditText etc). If there are no
copy/cut functions in View where the sensitive information (like personal information) is
input/output, information leakage will never happen from your application via Clipboard.
There are several methods to prohibit copying/cutting. This section herein describes the easy and
effective methods: One method is to disable long press View and another method is to delete
copy/cut items from menu when selecting character string. "OK" in Table 6.1-1 stands for realizable,
and "NG" stands for unrealizable. As per shown in Table 6.1-1, pay attention that copy/cut item
cannot be deleted from the menu of character string selection in the case of API level is 10 or earlier.
API Level
10 or eariler
OK
NG
11 or later
OK
OK
Necessary of counter-measure can be determined as per the flow of Figure 6.1-1. In Figure 6.1-1,
"Input type is fixed to Password attribute" means, the input type is necessarily either of the followings
three when application is running. In this case, no counter-measures are required since copy/cut are
prohibited as default.
376
InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD
InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD
InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD
http://www.jssec.org/dl/android_securecoding_en.pdf
Start
Yes
Input/Output
the sensitive information?
No
Yes
No Counter-measure needed
No
Prohibit copy/cut
6.1.1.1. Delete copy/cut from the menu when character string selection
TextView.setCustomSelectionActionMODECallback() method cannot be used in before Android
3.0(API Level 11). In this case, the easiest method to prohibit copying/cutting is to disable Long Click
View. Disabling Long Click View can be specified in layout xml file.
Sample code to delete copy/cut item from menu of character string selection in EditText, is shown as
per below.
Points:
1.
2.
UncopyableActivity.java
package org.jssec.android.clipboard.leakage;
import
import
import
import
import
import
import
android.app.Activity;
android.os.Bundle;
android.support.v4.app.NavUtils;
android.view.ActionMode;
android.view.Menu;
android.view.MenuItem;
android.widget.EditText;
377
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
378
(API Level 11). In this case, the easiest method to prohibit copying/cutting is to disable Long Click
View. Disabling Long Click View can be specified in layout xml file.
Point:
1.
unlongclickable.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/unlongclickable_description" />
<!-- EditText to prohibit copy/cut EditText -->
<!-- *** POINT 1 *** Set false to android:longClickable in View to prohibit copy/cut. -->
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:longClickable="false"
android:hint="@string/unlongclickable_hint" />
</LinearLayout>
379
1.
(Required)
(Required)
If there's a View which displays sensitive information in an application and besides the information is
allowed to be copied/cut like EditText in the View, the information may be leaked via Clipboard.
There are two methods to disable copy/cut. One method is to delete items of copy/cut from menu of
character string selection, and another method is to disable Long Click View. The former method can
and later version. And the latter one can be achieved in whatever before and after Android 3.0 (API
Level 11).
380
http://www.jssec.org/dl/android_securecoding_en.pdf
Android3.0(API Level 11) and later, possible to select/copy character strings or not can be
TextView, investigate the possibility that any sensitive information is displayed in TextView, and if
In addition, described in the decision flow of "6.1.1Sample Code" regarding EditText which is input
type (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD etc), supposing
password input, normally any counter-measures are not required since copying character strings are
prohibited as default. However, as described in "5.1.2.2 Provide the Option to Display Password in a
Plain Text
(Required)," when the option to [display password in a plain text] is prepared, in case of
displaying password in a plain text, input type will change and copy/cut is enabled. So the same
Note that, developers should also take usability of application into consideration when applying rules.
For example, in the case of View which user can input text freely, if copy/cut is disabled because
there is the slight possibility that sensitive information is input, users may feel inconvenience. Of
course, the rule should unconditionally be applied to View which treats highly important information
or independent sensitive information, but in the case of View other than those, the following
questions will help developers to understand how properly to treat View.
Prepare some other component for the exclusive use of sensitive information
Send information with alternative methods when the pasted-to application is obvious
Call users for cautions about inputting/outputting information
The root cause of the information leakage risk is that the specifications of Clipboard and
ClipboardManager in Android OS leave the security risk out of consideration. Application developers
need to create higher quality applications in terms of user integrity, usability, functions, and so forth.
Permission
for
using
ClipboardManager
and
thus
the
application
can
use
Here is how to realize the above mentioned implementation in API Level 11 and later, for your
reference.
381
http://www.jssec.org/dl/android_securecoding_en.pdf
Information,
called
ClipData,
stored
in
Clipboard
can
be
obtained
with
method
implementing
user. Therefore ClipData can be got without overlooking the timing. Listener call is executed when
The following shows the source code of Service, which gets ClipData whenever copy/cut is executed
in a device and displays it through Toast. You can realize that information stored in Clipboard is
leaked out doe to simple codes as follows. It's necessary to pay attention that the sensitive
information is not taken at least by the following source code.
ClipboardListeningService.java
package org.jssec.android.clipboard;
import
import
import
import
import
import
import
import
import
android.app.Service;
android.content.ClipData;
android.content.ClipboardManager;
android.content.ClipboardManager.OnPrimaryClipChangedListener;
android.content.Context;
android.content.Intent;
android.os.IBinder;
android.util.Log;
android.widget.Toast;
382
Next, below shows an example code of Activity which uses ClipboardListeningService touched in the
above.
ClipboardListeningActivity.java
package org.jssec.android.clipboard;
import
import
import
import
import
import
import
import
import
import
android.app.Activity;
android.content.ComponentName;
android.content.Intent;
android.os.Bundle;
android.support.v4.app.NavUtils;
android.util.Log;
android.view.Menu;
android.view.MenuItem;
android.view.View;
android.widget.Toast;
383
return true;
}
public void onClickStartService(View view) {
if (view.getId() != R.id.start_service_button) {
Log.w(TAG, "View ID is incorrect.");
} else {
if (!isServiceRunning) {
ComponentName cn = startService(
new Intent(ClipboardListeningActivity.this, ClipboardListeningService.class));
if (cn != null) {
this.isServiceRunning = true;
} else {
Log.e(TAG, "Failed to launch the service.");
Toast.makeText(this, "Failed to launch the service.", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(this, "Service has been already launched.", Toast.LENGTH_SHORT).show();
}
}
}
public void onClickStopService(View view) {
if (view.getId() != R.id.stop_service_button) {
Log.w(TAG, "View ID is incorrect.");
} else {
if (isServiceRunning) {
boolean res = stopService(
new Intent(ClipboardListeningActivity.this, ClipboardListeningService.class));
if (res) {
this.isServiceRunning = false;
} else {
Log.e(TAG, "Failed to stop the service.");
Toast.makeText(this, "Failed to stop the service.", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(this, "Service has been already stopped.", Toast.LENGTH_SHORT).show();
}
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
}
By the way, ClipboardManager in before API Level 11 does not have any method to register Listener
like setOnPrimaryClipChangedListener(). Hence, an application cannot get ClipData whenever copy
384
/cut operations are executed by user, but an application which is equivalent in behavior can be made
getPrimaryClip() method cannot be used. Therefore the information stored in Clipboard should be
obtained as character strings with getText() method. Sample codes for this are omitted here.
How to obtain information stored in Clipboard is described so far. On the other hand it is also
possible to store new information in Clipboard. In the case of API Level 11 or later,
ClipboardManager.setPrimaryClip() can be used. And in the case of before that API 11,
ClipboardManager.setText() method can be used.
Note that setPrimaryClip() or setText() method will overwrite the information stored in Clipboard,
therefore the information stored by user's copy/cut may be lost. When providing custom copy/cut
functions with these methods, it's necessary to design/implement in order not that the contents
stored in Clipboard are changed to unexpected contents, by displaying a dialogue to notify the
contents are to be changed, according the necessity.
385