sdk doc change: Added KeyChain API Demo
Change-Id: Ib2b751a9de485359705b243d2c70e5f8f2c8ab3c
This commit is contained in:
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 "";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user