package com.rim.samples.device.smartcard;
import net.rim.device.api.crypto.*;
import net.rim.device.api.crypto.certificate.*;
import net.rim.device.api.crypto.certificate.x509.*;
import net.rim.device.api.crypto.keystore.*;
import net.rim.device.api.smartcard.*;
import net.rim.device.api.util.*;
/*******************************************************************************
* This class represents a communication session with a physical smart card.
*
* Application Protocol Data Units (APDUs) can be sent to the smart card over a
* session to provide the desired functionality.
*
* Do not keep sessions open when you are not using them. They should be
* short-lived. As a security precaution, only one open session is allowed
* per SmartCardReader.Subsequent openSession() requests will block until the
* current session is closed.
*/
public class MyCryptoSmartCardSession extends CryptoSmartCardSession
{
// We assume that the smart card has three certificates identified
// by: ID_PKI, SIGNING_PKI and ENCRYPION_PKI. Your smart card may have a
// different number of certificates or be identified differently. These
// three certificates are just an example of what might be stored on a
// smart card.
public static final byte ID_PKI = (byte)0x00;
public static final byte SIGNING_PKI = (byte)0x01;
public static final byte ENCRYPTION_PKI = (byte)0x02;
private static final String WAITING_MSG = "Please Wait";
private static final String ID_STRING = "Jason Hood";
private static final String ID_CERT = "ID Certificate";
private static final String SIGNING_CERT = "Signing Certificate";
private static final String ENCRYPTION_CERT = "Encryption Certificate";
/**************************************************************************
* Provide a constructor for the session.
*
* The smartCard argument is the smart card associated with this session.
* The readerSession argument holds the reader session commands sent to this
* smart card.
***************************************************************************/
protected MyCryptoSmartCardSession(SmartCard smartCard, SmartCardReaderSession readerSession)
{
super( smartCard, readerSession );
}
/**************************************************************************
* Provide a method to close the session.
*
* Do not close the underlying SmartCardReaderSession.Use this method to
* clean up the session prior to its closure.
***************************************************************************
/
protected void closeImpl()
{
// Do any session cleanup needed here.
}
/**************************************************************************
* Provide a method that returns the maximum number of allowed login
* attempts. Return Integer.MAX_VALUE to allow an infinite number of login
* attempts.
***************************************************************************/
protected int getMaxLoginAttemptsImpl() throws SmartCardException
{
return 5;
}
/**************************************************************************
* Provide a method that returns the number of allowed login attempts
* remaining (before the smart card will lock). Return Integer.MAX_VALUE if
* the smart card will never lock.
***************************************************************************
/
protected int getRemainingLoginAttemptsImpl() throws SmartCardException
{
return 4;
}
/**************************************************************************
* Provide a method that logs into the smart card with a specified password.
* This method should not implement UI.
***************************************************************************/
protected boolean loginImpl( String password ) throws SmartCardException
{
// Create the APDU command for your smart card. Consult documentation
// from your smartcard vendor.
CommandAPDU command =
new CommandAPDU( (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x00 );
command.setLcData( password.getBytes() );
ResponseAPDU response = new ResponseAPDU();
sendAPDU( command, response );
// Check for response codes specific to your smart card.
if ( response.checkStatusWords( (byte)0x90, (byte)0x00 ) )
{
return true;
}
else if ( response.checkStatusWords( (byte)0x64, (byte)0xF8 ) )
{
throw new SmartCardLockedException();
}
else
{
// Authentication failed
return false;
}
}
/**************************************************************************
* Provide a method that returns an ID for the smart card associated with
* this session.
***************************************************************************/
protected SmartCardID getSmartCardIDImpl() throws SmartCardException
{
// Retrieve a unique ID from the card
byte [] uniqueCardData = new byte[]
{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b };
// Convert the byte array to a long
SHA1Digest digest = new SHA1Digest();
digest.update( uniqueCardData );
long idLong = byteArrayToLong(Arrays.copy(digest.getDigest(), 0, 8));
return new SmartCardID( idLong , ID_STRING, getSmartCard() );
}
/**************************************************************************
* Private method to convert a byte array into a long integer. Note that the
* return value should be interpretted as an unsigned value.
*
* The method throws an IllegalArgumentException if the byte array contains
* a number larger than 64 bits.
*
* Note: If your smart card driver is only designed to work with BlackBerry
* Version 4.2 or later, you can replace this method with a call to
* CryptoByteArrayArithmetic.valueOf( byte [] ).
**************************************************************************/
private long byteArrayToLong( byte[] array )
{
if ( array == null )
{
throw new IllegalArgumentException();
}
// Remove the leading zeros from the specified byte array and return
// a new byte array without them.
int zeros = 0;
for ( int i = 0; i < array.length && array[i] == 0; i++ )
{
zeros++;
}
if ( zeros != 0 )
{
array = Arrays.copy( array, zeros, array.length - zeros );
}
int length = array.length;
if( length > 8 )
{
throw new IllegalArgumentException();
}
long n = 0;
for( int i=0; i<length; i++ )
{
n <<= 8;
n += array[i] & 0xff;
}
return n;
}
/**************************************************************************
* Provide a method that returns random data from the smart cards internal
* random number generator.
***************************************************************************/
protected byte [] getRandomBytesImpl( int maxBytes ) throws SmartCardException
{
// Create the appropriate CommandAPDU for your smart card. See your
// smart card vendor's documentation.
CommandAPDU command =
new CommandAPDU((byte)0x00,(byte)0x4C,(byte)0x00,(byte)0x00,maxBytes);
ResponseAPDU response = new ResponseAPDU();
sendAPDU( command, response );
// Check for response codes specific to your smart card
if( response.checkStatusWords( (byte)0x90, (byte)0x00 ) )
{
// return the response code containing the random data
return response.getData();
}
return null;
}
/**************************************************************************
* Provide a method that returns certificates from the smart card.
* Return an array containing copies of certificates on the card.
***************************************************************************/
protected CryptoSmartCardKeyStoreData[] getKeyStoreDataArrayImpl()
throws SmartCardException, CryptoTokenException
{
try
{
// Display a progress dialog because this operation may take a long
// time.
displayProgressDialog( WAITING_MSG, 4 );
// Associate the certificates with a particular card.
SmartCardID smartCardID = getSmartCardID();
RSACryptoToken token = new MyRSACryptoToken();
RSACryptoSystem cryptoSystem = new RSACryptoSystem( token, 1024 );
RSAPrivateKey privateKey;
CryptoSmartCardKeyStoreData[] keyStoreDataArray = new
CryptoSmartCardKeyStoreData[ 3 ];
// This encoding would be extracted from the card using a series of
// APDU commands.
Certificate certificate = null;
// Extract the certificate encoding from the card.
byte [] certificateEncoding = new byte[0];
try
{
certificate = new X509Certificate( certificateEncoding );
}
catch( CertificateParsingException e )
{
// invalid X509 certificate
}
}
stepProgressDialog( 1 );
privateKey = new RSAPrivateKey(cryptoSystem,
new MyCryptoTokenData( smartCardID, ID_PKI ) );
keyStoreDataArray[ 0 ] =
new CryptoSmartCardKeyStoreData( null, ID_CERT, privateKey, null,
KeyStore.SECURITY_LEVEL_HIGH, certificate, null, null, 0 );
stepProgressDialog( 1 );
privateKey = new RSAPrivateKey( cryptoSystem,
new MyCryptoTokenData( smartCardID, SIGNING_PKI ) );
keyStoreDataArray[ 1 ] =
new CryptoSmartCardKeyStoreData( null, SIGNING_CERT, privateKey,
null, KeyStore.SECURITY_LEVEL_HIGH, certificate, null, null, 0 );
stepProgressDialog( 1 );
privateKey = new RSAPrivateKey( cryptoSystem,
new MyCryptoTokenData( smartCardID, ENCRYPTION_PKI ) );
keyStoreDataArray[ 2 ] = new CryptoSmartCardKeyStoreData( null,
ENCRYPTION_CERT, privateKey, null,
KeyStore.SECURITY_LEVEL_HIGH, certificate, null, null, 0 );
stepProgressDialog( 1 );
// Sleep so the user sees the last step of the progress dialog
// as it moves to 100%
try
{
Thread.sleep( 250 );
}
catch ( InterruptedException e )
{
}
dismissProgressDialog();
return keyStoreDataArray;
}
catch ( CryptoUnsupportedOperationException e )
{
}
catch ( UnsupportedCryptoSystemException e )
{
}
catch ( CryptoTokenException e )
{
}
throw new SmartCardException();
}
/**************************************************************************
* Send some data to the smart card for signing or decryption.
***************************************************************************/
/*package*/ void signDecrypt( RSACryptoSystem cryptoSystem,
MyCryptoTokenData privateKeyData,byte[] input, int inputOffset,
byte[] output, int outputOffset ) throws SmartCardException
{
// Check for nulls
if ( cryptoSystem == null || privateKeyData == null || input == null || output == null) {
throw new IllegalArgumentException();
}
// Validate the input parameters
int modulusLength = cryptoSystem.getModulusLength();
if ( ( input.length < inputOffset + modulusLength ) || ( output.length < outputOffset +
modulusLength ) ) {
throw new IllegalArgumentException();
}
// Construct the response Application Protocol Data Unit
ResponseAPDU response = new ResponseAPDU();
// Construct the command and set its information
// Create a CommandAPDU which your smart card will understand
CommandAPDU signAPDU = new CommandAPDU( (byte)0x80, (byte)0x56, (byte)0x00,
(byte)0x00, modulusLength );
signAPDU.setLcData( input, inputOffset, input.length - inputOffset );
// Send the command to the smart card
sendAPDU( signAPDU, response );
// Validate the status words of the response
// Check for response codes specific to your smart card
if ( response.checkStatusWords( (byte)0x90, (byte)0x00 ) ) {
byte [] responseData = response.getData();
System.arraycopy( responseData, 0, output, outputOffset, responseData.length );
}
else {
throw new SmartCardException( “Invalid response code, sw1=” + Integer.toHexString(
response.getSW1() & 0xff ) + “ sw2=” + Integer.toHexString( response.getSW2() & 0xff ) );
}
}
}