sdk doc change: Added KeyChain API Demo

Change-Id: Ib2b751a9de485359705b243d2c70e5f8f2c8ab3c
This commit is contained in:
Tony Chan
2012-03-29 02:03:59 +08:00
parent 64cd8f35fb
commit 1a2ddb4216
14 changed files with 836 additions and 0 deletions

View File

@@ -0,0 +1,325 @@
/*
* Copyright 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.keychain;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.net.Uri;
import android.os.Bundle;
import android.security.KeyChain;
import android.security.KeyChainAliasCallback;
import android.security.KeyChainException;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
public class KeyChainDemoActivity extends Activity implements
KeyChainAliasCallback {
/**
* The file name of the PKCS12 file used
*/
public static final String PKCS12_FILENAME = "keychain.p12";
/**
* The pass phrase of the PKCS12 file
*/
public static final String PKCS12_PASSWORD = "changeit";
/**
* Intent extra name to indicate to stop server
*/
public static final String EXTRA_STOP_SERVER = "stop_server";
// Log tag for this class
private static final String TAG = "KeyChainApiActivity";
// Alias for certificate
private static final String DEFAULT_ALIAS = "My Key Chain";
// Name of the application preference
private static final String KEYCHAIN_PREF = "keychain";
// Name of preference name that saves the alias
private static final String KEYCHAIN_PREF_ALIAS = "alias";
// Request code used when starting the activity using the KeyChain install
// intent
private static final int INSTALL_KEYCHAIN_CODE = 1;
// Test SSL URL
private static final String TEST_SSL_URL = "https://localhost:8080";
// Button to start/stop the simple SSL web server
private Button serverButton;
// Button to install the key chain
private Button keyChainButton;
// Button to launch the browser for testing https://localhost:8080
private Button testSslButton;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set the view using the main.xml layout
setContentView(R.layout.main);
// Check whether the key chain is installed or not. This takes time and
// should be done in another thread other than the main thread.
new Thread(new Runnable() {
@Override
public void run() {
if (isKeyChainAccessible()) {
// Key chain installed. Disable the install button and print
// the key chain information
disableKeyChainButton();
printInfo();
} else {
Log.d(TAG, "Key Chain is not accessible");
}
}
}).start();
// Setup the key chain installation button
keyChainButton = (Button) findViewById(R.id.keychain_button);
keyChainButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
installPkcs12();
}
});
// Setup the simple SSL web server start/stop button
serverButton = (Button) findViewById(R.id.server_button);
serverButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (serverButton.getText().equals(
getResources().getString(R.string.server_start))) {
serverButton.setText(R.string.server_stop);
startServer();
} else {
serverButton.setText(R.string.server_start);
stopServer();
}
}
});
// Setup the test SSL page button
testSslButton = (Button) findViewById(R.id.test_ssl_button);
testSslButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent i = new Intent(Intent.ACTION_VIEW, Uri
.parse(TEST_SSL_URL));
startActivity(i);
}
});
}
/**
* This will be called when the user click on the notification to stop the
* SSL server
*/
@Override
protected void onNewIntent(Intent intent) {
Log.d(TAG, "In onNewIntent()");
super.onNewIntent(intent);
boolean isStopServer = intent.getBooleanExtra(EXTRA_STOP_SERVER, false);
if (isStopServer) {
serverButton.setText(R.string.server_start);
stopServer();
}
}
/**
* This implements the KeyChainAliasCallback
*/
@Override
public void alias(String alias) {
if (alias != null) {
setAlias(alias); // Set the alias in the application preference
disableKeyChainButton();
printInfo();
} else {
Log.d(TAG, "User hit Disallow");
}
}
/**
* This method returns the alias of the key chain from the application
* preference
*
* @return The alias of the key chain
*/
private String getAlias() {
SharedPreferences pref = getSharedPreferences(KEYCHAIN_PREF,
MODE_PRIVATE);
return pref.getString(KEYCHAIN_PREF_ALIAS, DEFAULT_ALIAS);
}
/**
* This method sets the alias of the key chain to the application preference
*/
private void setAlias(String alias) {
SharedPreferences pref = getSharedPreferences(KEYCHAIN_PREF,
MODE_PRIVATE);
Editor editor = pref.edit();
editor.putString(KEYCHAIN_PREF_ALIAS, alias);
editor.commit();
}
/**
* This method prints the key chain information.
*/
private void printInfo() {
String alias = getAlias();
X509Certificate[] certs = getCertificateChain(alias);
final PrivateKey privateKey = getPrivateKey(alias);
final StringBuffer sb = new StringBuffer();
for (X509Certificate cert : certs) {
sb.append(cert.getIssuerDN());
sb.append("\n");
}
runOnUiThread(new Runnable() {
@Override
public void run() {
TextView certTv = (TextView) findViewById(R.id.cert);
TextView privateKeyTv = (TextView) findViewById(R.id.private_key);
certTv.setText(sb.toString());
privateKeyTv.setText(privateKey.getFormat() + ":" + privateKey);
}
});
}
/**
* This method will launch an intent to install the key chain
*/
private void installPkcs12() {
try {
BufferedInputStream bis = new BufferedInputStream(getAssets().open(
PKCS12_FILENAME));
byte[] keychain = new byte[bis.available()];
bis.read(keychain);
Intent installIntent = KeyChain.createInstallIntent();
installIntent.putExtra(KeyChain.EXTRA_PKCS12, keychain);
installIntent.putExtra(KeyChain.EXTRA_NAME, DEFAULT_ALIAS);
startActivityForResult(installIntent, INSTALL_KEYCHAIN_CODE);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == INSTALL_KEYCHAIN_CODE) {
switch (resultCode) {
case Activity.RESULT_OK:
chooseCert();
break;
default:
super.onActivityResult(requestCode, resultCode, data);
}
}
}
private void chooseCert() {
KeyChain.choosePrivateKeyAlias(this, this, // Callback
new String[] {}, // Any key types.
null, // Any issuers.
"localhost", // Any host
-1, // Any port
DEFAULT_ALIAS);
}
private X509Certificate[] getCertificateChain(String alias) {
try {
return KeyChain.getCertificateChain(this, alias);
} catch (KeyChainException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
private PrivateKey getPrivateKey(String alias) {
try {
return KeyChain.getPrivateKey(this, alias);
} catch (KeyChainException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
/**
* This method checks if the key chain is installed
*
* @return true if the key chain is not installed or allowed
*/
private boolean isKeyChainAccessible() {
return getCertificateChain(getAlias()) != null
&& getPrivateKey(getAlias()) != null;
}
/**
* This method starts the background service of the simple SSL web server
*/
private void startServer() {
Intent secureWebServerIntent = new Intent(this,
SecureWebServerService.class);
startService(secureWebServerIntent);
}
/**
* This method stops the background service of the simple SSL web server
*/
private void stopServer() {
Intent secureWebServerIntent = new Intent(this,
SecureWebServerService.class);
stopService(secureWebServerIntent);
}
/**
* This is a convenient method to disable the key chain install button
*/
private void disableKeyChainButton() {
runOnUiThread(new Runnable() {
@Override
public void run() {
keyChainButton.setText(R.string.keychain_installed);
keyChainButton.setEnabled(false);
}
});
}
}

View File

@@ -0,0 +1,185 @@
/*
* Copyright 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.keychain;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.security.KeyStore;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import android.content.Context;
import android.util.Base64;
import android.util.Log;
public class SecureWebServer {
// Log tag for this class
private static final String TAG = "SecureWebServer";
// File name of the image used in server response
private static final String EMBEDDED_IMAGE_FILENAME = "training-prof.png";
private SSLServerSocketFactory sssf;
private SSLServerSocket sss;
// A flag to control whether the web server should be kept running
private boolean isRunning = true;
// The base64 encoded image string used as an embedded image
private final String base64Image;
/**
* WebServer constructor.
*/
public SecureWebServer(Context ctx) {
try {
// Get an SSL context using the TLS protocol
SSLContext sslContext = SSLContext.getInstance("TLS");
// Get a key manager factory using the default algorithm
KeyManagerFactory kmf = KeyManagerFactory
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
// Load the PKCS12 key chain
KeyStore ks = KeyStore.getInstance("PKCS12");
FileInputStream fis = ctx.getAssets()
.openFd(KeyChainDemoActivity.PKCS12_FILENAME)
.createInputStream();
ks.load(fis, KeyChainDemoActivity.PKCS12_PASSWORD.toCharArray());
kmf.init(ks, KeyChainDemoActivity.PKCS12_PASSWORD.toCharArray());
// Initialize the SSL context
sslContext.init(kmf.getKeyManagers(), null, null);
// Create the SSL server socket factory
sssf = sslContext.getServerSocketFactory();
} catch (Exception e) {
e.printStackTrace();
}
// Create the base64 image string used in the server response
base64Image = createBase64Image(ctx);
}
/**
* This method starts the web server listening to the port 8080
*/
protected void start() {
new Thread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "Secure Web Server is starting up on port 8080");
try {
// Create the secure server socket
sss = (SSLServerSocket) sssf.createServerSocket(8080);
} catch (Exception e) {
System.out.println("Error: " + e);
return;
}
Log.d(TAG, "Waiting for connection");
while (isRunning) {
try {
// Wait for an SSL connection
Socket socket = sss.accept();
// Got a connection
Log.d(TAG, "Connected, sending data.");
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket
.getOutputStream());
// Read the data until a blank line is reached which
// signifies the end of the client HTTP headers
String str = ".";
while (!str.equals(""))
str = in.readLine();
// Send a HTTP response
out.println("HTTP/1.0 200 OK");
out.println("Content-Type: text/html");
out.println("Server: Android KeyChainiDemo SSL Server");
// this blank line signals the end of the headers
out.println("");
// Send the HTML page
out.println("<H1>Welcome to Android!</H1>");
// Add an embedded Android image
out.println("<img src='data:image/png;base64," + base64Image + "'/>");
out.flush();
socket.close();
} catch (Exception e) {
Log.d(TAG, "Error: " + e);
}
}
}
}).start();
}
/**
* This method stops the SSL web server
*/
protected void stop() {
try {
// Break out from the infinite while loop in start()
isRunning = false;
// Close the socket
if (sss != null) {
sss.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* This method reads a binary image from the assets folder and returns the
* base64 encoded image string.
*
* @param ctx The service this web server is running in.
* @return String The base64 encoded image string or "" if there is an
* exception
*/
private String createBase64Image(Context ctx) {
BufferedInputStream bis;
try {
bis = new BufferedInputStream(ctx.getAssets().open(EMBEDDED_IMAGE_FILENAME));
byte[] embeddedImage = new byte[bis.available()];
bis.read(embeddedImage);
return Base64.encodeToString(embeddedImage, Base64.DEFAULT);
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.keychain;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
public class SecureWebServerService extends Service {
// Log tag for this class
private static final String TAG = "SecureWebServerService";
// A special ID assigned to this on-going notification.
private static final int ONGOING_NOTIFICATION = 1248;
// A handle to the simple SSL web server
private SecureWebServer sws;
/**
* Start the SSL web server and set an on-going notification
*/
@Override
public void onCreate() {
super.onCreate();
sws = new SecureWebServer(this);
sws.start();
createNotification();
}
/**
* Stop the SSL web server and remove the on-going notification
*/
@Override
public void onDestroy() {
super.onDestroy();
sws.stop();
stopForeground(true);
}
/**
* Return null as there is nothing to bind
*/
@Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* Create an on-going notification. It will stop the server when the user
* clicks on the notification.
*/
private void createNotification() {
Log.d(TAG, "Create an ongoing notification");
Intent notificationIntent = new Intent(this,
KeyChainDemoActivity.class);
notificationIntent.putExtra(KeyChainDemoActivity.EXTRA_STOP_SERVER,
true);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
notificationIntent, 0);
Notification notification = new Notification.Builder(this).
setContentTitle(getText(R.string.notification_title)).
setContentText(getText(R.string.notification_message)).
setSmallIcon(android.R.drawable.ic_media_play).
setTicker(getText(R.string.ticker_text)).
setOngoing(true).
setContentIntent(pendingIntent).
getNotification();
startForeground(ONGOING_NOTIFICATION, notification);
}
}