BlackBerry 应用程序开发者指南 第二卷:高级--第14章 增加对智能卡(smart card)的支持
作者:Confach 发表于 2006-04-28 22:28 pm
版权信息:可以任意转载, 转载时请务必以超链接形式标明文章原始出处 和作者信息.
http://www.cnblogs.com/confach/articles/388978.html
14
第14章 增加对智能卡(smart card)的支持
使用智能卡 创建一个智能卡驱动 |
使用智能卡
智能卡和信用卡一般大,可以安全的存储和传输敏感数据.BlackBerry设备支持下面的智能卡:
- 普通访问卡(CAC,Common Access Card)
- SafeNet智能卡(Datakey Model 330)
如果你使用的智能卡不是CAC或SageNet智能卡,你也可以利用智能卡的API编写一个BlackBerry设备驱动来支持你的智能卡.智能卡API提供了一个组件库,它们在net.rim.device.api.smartcard 包中,用来和智能卡以及智能卡读卡器进行交互.
对智能卡API恰当实现了的驱动,可以和BlackBerry设备上已经启动的S/MIME一起工作.它包括从智能卡导入认证,以及对卡进行私有键操作(签名和解密消息)
注:为获取关于S/MIME的更多信息,参看带有S/MIME支持包的BlackBerry。
创建一个智能卡驱动
为创建一个智能卡驱动,完成下面的步骤:
1. 扩展CryptoSmartCard类,实现一个新的智能卡类.
2. 在新的智能卡里,实现libmain()方法,在启动的时候调用它.
3. 扩展CryptoSmartCardSession类,未智能卡类实现一个新的对话.
4. 扩展Crypto环(token)类,为RSA, DSA, ECC操作实现一个环。
5. 存储私有键文件位置.
注:为得到更多关于net.rim.device.api.crypto包的信息,参看API参考.
实现一个智能卡
扩展抽象类CryptoSmartCard.此类的扩展允许子类作为一个智能卡驱动在SmartCardFactory里注册.子类必须实现下面SmartCard和CryptoSmartCard抽象类的方法:
SmartCard 方法 |
描述 |
openSessionImpl(SmartCardReaderSession) |
建立一个和智能卡的通信. |
checkAnswerToResetImpl(AnswerToReset) |
验证智能卡和一个指定的ATR的兼容性. |
displaySettingsImpl(Object) |
允许智能卡驱动显示设置或属性. |
isDisplaySettingsAvailableImpl(Object) |
描述驱动是否支持显示设置. |
getCapabilitiesImpl() |
获取智能卡的能力. |
getLabelImpl() |
获取智能卡的类型. |
toString() |
得到智能卡的字符串描述. |
CryptoSmartCard 方法 |
描述 |
getAlgorithms() |
得到智能卡支持的所有算法名.例如“RSA”, “DSA” |
getCryptoToken(String) |
获取一个支持给定算法的加密环. |
代码实例
.下面的实例描述了通过扩展CryptoSmartCard类,如何创建一个智能卡对象.
例: MyCryptoSmartCard.java
/**
* MyCryptoSmartCard.java
* Copyright (C) 2001-2005 Research In Motion Limited. All rights reserved.
*/
package com.rim.samples.device.smartcard;
import net.rim.device.api.smartcard.*;
import net.rim.device.api.util.*;
import net.rim.device.api.crypto.*;
import net.rim.device.api.ui.component.*;
/**
* This class represents a kind (or model or family) of a physical smart card.
* There should only be one instance of this class in the system at one time. The instance
* is managed by the SmartCardFactory.
*/
public class MyCryptoSmartCard extends CryptoSmartCard implements Persistable
{
private final static byte MY_ATR [] = {
(byte)0x3b, (byte)0x7d, (byte)0x11,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x31,
(byte)0x80, (byte)0x71, (byte)0x8e, (byte)0x64,
(byte)0x86, (byte)0xd6, (byte)0x01, (byte)0x00,
(byte)0x81, (byte)0x90, (byte)0x00 };
private final static AnswerToReset _myATR = new AnswerToReset( MY_ATR );
private static final String LABEL = "RIM Sample";
private static final String DISPLAY_SETTINGS = "Show driver properties/settings now";
private static final String RSA = "RSA";
/**
*A smart card factory’s list of known smart cards.
*Called on startup of the device. Register this driver with the
*/
public static void libMain( String args[] )
{
SmartCardFactory.addSmartCard( new MyCryptoSmartCard() );
}
/**
*Retrieve the session handler for this smart card.
*Implementations of this method should not bring up UI.
*/
protected SmartCardSession openSessionImpl( SmartCardReaderSession readerSession )
throws SmartCardException {
return new MyCryptoSmartCardSession( this, readerSession );
}
/**
* Determine if the file system should use this smart card object
* to communicate with a physical smart card.
* that has the given AnswerToReset.
* The system invokes this method to ascertain which smart card implementation it should
* use to communicate with a physical smart card found in a reader.
*/
protected boolean checkAnswerToResetImpl( AnswerToReset atr )
{
return _myATR.equals( atr );
}
/**
*Retrieve a label associated with this smart card.
*The string should not include the words "smart card", as the file system uses this
*this method to generate strings such as "Please insert your smart card".
*/
protected String getLabelImpl()
{
return LABEL;
}
/**
* Retrieves this smart card’s capabilities
*/
protected SmartCardCapabilities getCapabilitiesImpl()
{
return new SmartCardCapabilities( SmartCardCapabilities.PROTOCOL_T0 );
}
/**
*Determine if this smart card can display its settings.
*/
protected boolean isDisplaySettingsAvailableImpl( Object context )
{
return true;
}
/**
*Display this smart card’s settings.
*This method will be invoked from the smart card options screen when
*the user selects the driver and chooses to view the settings of that driver.
*
*This method could be called from the event thread. The driver should not block
*the event thread for long periods of time.
*
*@param context Reserved for future use.
**/
protected void displaySettingsImpl( Object context )
{
Dialog.alert( DISPLAY_SETTINGS );
}
/**
* Retrieve the algorithms supported by this smart card.
*
* @return one of "RSA", "DSA", or "ECC"
*/
public String[] getAlgorithms()
{
return new String [] { RSA };
}
/** Retrieve a crypto token that supports the given algorithm.
* @param algorithm Name of the algorithm. *
* @return Crypto Token supporting the named algorithm.
* @throws NoSuchAlgorithmException If the specified algorithm is invalid.
* @throws CryptoTokenException If there is a token-related problem.
*/
public CryptoToken getCryptoToken( String algorithm )
throws NoSuchAlgorithmException, CryptoTokenException
{
if ( algorithm.equals( RSA ) )
{
return new MyRSACryptoToken();
}
throw new NoSuchAlgorithmException();
}
}
在启动时打开libMain()
当扩展CryptoSmartCard类时,你需要创建一个libMain()方法.为了使此方法恰当的运行,设置BlackBerry IDE在启动时运行此方法:
1.启动BlackBerry IDE.
2.为你正在设计的智能卡驱动创建一个新的工程
注: 参看BlackBerryIDE在线帮助获得更多信息.
3.在Workspace窗口,右击你创建的工程.
4.选择 Properties.
5.选择Application标签..
6.在Project type 域, 输入Library.
7.务必选择 Auto-run on startup.
8.在Startup Tier域, 选择7(Last; 3rd party apps only) 选项.
9.单击OK.
实现一个智能卡对话
扩展CryptoSmartCardSession抽象类.你的子类必须实现下列SmartCardSession和CryptoSmartCardSession抽象类的方法.
SmartCardSession 方法 |
描述 |
closeImpl() |
结束一个智能卡对话。 |
getMaxLoginAttemptsImpl() |
获取试图登录的最大次数。 |
getRemainingLoginAttemptsImpl() |
获取试图登录的剩余次数。 |
getSmartCardIDImpl() |
获取智能卡ID。 |
loginImpl(String) |
试图用一个给定的密码将用户记录到智能卡。 |
CryptoSmartCardSession 方法 |
描述 |
getKeyStoreDataArrayImpl() |
获取与卡上存储的键相关联的键存储数据. |
getRandomBytesImpl(int maxNumBytes) |
获取一些来自智能卡内部随机数生成器生成的随机数据. |
代码实例
下面的实例描述了如何扩展CryptoSmartCardSession类创建一个智能卡对话:
例: MyCryptoSmartCardSession.java
/**
* MyCryptoSmartCardSession.java
* Copyright (C) 2001-2005 Research In Motion Limited. All rights reserved.
*/
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.
* Over this session, Application Protocol Data Units may be exchanged with the smart card
* to provide the desired functionality.
* Do not hold open sessions when not using them; they should be short-lived.
* As a security precaution, only one open session is allowed to exist per SmartCardReader;
* subsequent openSession() requests will block until the current session is closed.
*/
public class MyCryptoSmartCardSession extends CryptoSmartCardSession
{
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 = "John H. Smith";
private static final String ID_CERT = "ID Certificate";
private static final String SIGNING_CERT = "Signing Certificate";
private static final String ENCRYPTION_CERT = "Encryption Certificate";
/**
* Construct a new MyCryptoSmartCardSession object.
* @param smartCard Smart card associated with this session
* @param readerSession Reader session commands sent to this smart card.
*/
protected MyCryptoSmartCardSession( SmartCard smartCard, SmartCardReaderSession readerSession )
{
super( smartCard, readerSession );
}
/**
* Close this smart card session.
*
* Implementations should not close the underlying SmartCardReaderSession.
* Use this method for cleaning up the session prior to its closure.
*/
protected void closeImpl()
{
// Do any session cleanup needed here.
}
/**
* Retrieve the maximum number of allowed login attempts.
* The method returns Integer.MAX_VALUE if an infinite number of attempts are allowed.
*/
protected int getMaxLoginAttemptsImpl() throws SmartCardException
{
return 5;
}
/**
* Retrieve the remaining number of login attempts allowed (before the smart card will
* lock, or Integer.MAX_VALUE if the smart card will not lock.)
*/
protected int getRemainingLoginAttemptsImpl() throws SmartCardException
{
return 4;
}
/**
* Log into the smart card with the given password.
* This method should not bring up the UI.
*/
protected boolean loginImpl( String password ) throws SmartCardException
{
// Create a CommandAPDU which your smart card will understand
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;
}
}
/**
* Retrieve an ID for this session’s associated smart card.
*/
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 byte array to a long
SHA1Digest digest = new SHA1Digest();
digest.update( uniqueCardData );
byte [] bytesToUse = Arrays.copy( digest.getDigest(), 0, 8 );
long idLong = CryptoByteArrayArithmetic.valueOf( bytesToUse );
// Using friendly display name
return new SmartCardID( idLong , ID_STRING, getSmartCard() );
}
/**
* Retrieve some random data from the smart cards internal Random Number Generator.
*/
protected byte [] getRandomBytesImpl( int maxBytes ) throws SmartCardException
{
// Create a CommandAPDU which your smart card will understand
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 ) ) {
// The appropriate response code containing the random data
return response.getData();
}
return null;
}
/**
* Retrieve certificates from the card.
* @return An array of certificates which are present on the card.
*/
protected CryptoSmartCardKeyStoreData[] getKeyStoreDataArrayImpl()
throws SmartCardException, CryptoTokenException
{
try {
// Show a progress dialog to the user as this operation
//may take a long time.
displayProgressDialog( WAITING_MSG, 4 );
// The certificates need to be associated 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 move 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 smartcard
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 ) );
}
}
}
启动一个Crypto环
实现的环取决一卡的键类型((RSA, DSA, 或 ECC).参看API参考获得更多信息.
代码实例
下面的例子描述了如何为私有键RSA操作使用一个RSACryptoToken类的扩展.
例: MyRSACryptoToken.java
/* MyRSACryptoToken.java
* Copyright (C) 2001-2005 Research In Motion Limited. All rights reserved.
*/
package com.rim.samples.device.smartcard;
import net.rim.device.api.smartcard.*;
import net.rim.device.api.crypto.*;
import net.rim.device.api.crypto.keystore.*;
import net.rim.device.api.util.*;
import net.rim.device.api.crypto.certificate.x509.*;
import net.rim.device.api.crypto.certificate.*;
import net.rim.device.api.crypto.asn1.*;
import net.rim.device.api.compress.*;
import net.rim.device.api.i18n.*;
/**
* This class describes an implmentation of an RSA cryptographic token.
*
* The RIM Crypto API will use this object to perform a private key RSA operation.
* This object should delegate the operation to the smart card.
*/
final class MyRSACryptoToken extends RSACryptoToken implements Persistable
{
private static final String DECRYPT_DESC =
"The private key will be used to decrypt encrypted data.";
private static final String SIGN_DESC = "The private key will be used to generate a digital signature.";
/**
* Constructs a new MyRSACryptoToken object.
*/
MyRSACryptoToken()
{
}
/**
* Determine if this token does the user authentication for the system.
*
* If not the KeyStore will prompt for the key store password when the user
* tries to access the private key.
*
* @return True if this token will prompt for the necessary
* user authentication when private key access is requested.
*/
public boolean providesUserAuthentication()
{
return true;
}
/**
* Determine if this token supports the chosen operation using the provided system.
*
* @param cryptoSystem Crypto System to check against.
* @param operation Operation to check: either KEY_GENERATION,
* PUBLIC_KEY_OPERATION, PRIVATE_KEY_OPERATION,* or some other value
* specific to the crypto system that indicates the operation to check.
*/
public boolean isSupported( CryptoSystem cryptoSystem, int operation )
{
return ( operation == PRIVATE_KEY_OPERATION );
}
/**
* Determines if the given key and crypto system
* support RSA encryption.
*
* @return True if the token supports RSA encryption.
* @param cryptoSystem Crypto system to check.
* @param privateKeyData Private key data.
* @throws CryptoTokenException If an error occurs with a crypto
* token or the crypto token is invalid.
*/
public boolean isSupportedDecryptRSA( RSACryptoSystem cryptoSystem, CryptoTokenPrivateKeyData privateKeyData )
throws CryptoTokenException
{
return privateKeyData instanceof MyCryptoTokenData;
}
/**
* Perform a raw RSA decryption.
*
* @param cryptoSystem Crypto system associated with the token.
* @param privateKeyData RSA private key.
* @param input Input data.
* @param inputOffset First byte of the input data to read.
* @param output Buffer for the output data.
* @param outputOffset Position in the output buffer to receive the first written byte.
*
* @throws CryptoTokenException Thrown if an error occurs with a crypto
* token or the crypto token is invalid.
*/
public void decryptRSA( RSACryptoSystem cryptoSystem,
CryptoTokenPrivateKeyData privateKeyData,byte[] input, int inputOffset,
byte[] output, int outputOffset )throws CryptoTokenException
{
try
{
signDecryptHelper( cryptoSystem, privateKeyData, input, inputOffset, output, outputOffset, DECRYPT_DESC, SmartCardSession.DECRYPT_OPERATION );
}
catch ( CryptoUnsupportedOperationException e ) {
throw new CryptoTokenException( e.toString() );
}
}
/**
* Perform a raw RSA signing.
*
* @param cryptoSystem Cypto system associated with the token.
* @param privateKeyData RSA private key.
* @param input Input data.
* @param inputOffset First byte of the input data to read.
* @param outputOffset Position in the output buffer to receive the first written byte.
* @throws CryptoTokenException If an error occurs with the crypto
* token or the crypto token is invalid.
* @throws CryptoUnsupportedOperationException If a call is made to
* an unsupported operation.
*/
public void signRSA( RSACryptoSystem cryptoSystem,
CryptoTokenPrivateKeyData privateKeyData, byte[] input,
int inputOffset,byte[] output, int outputOffset )
throws CryptoTokenException, CryptoUnsupportedOperationException
{
signDecryptHelper( cryptoSystem, privateKeyData, input, inputOffset,
output, outputOffset, SIGN_DESC, SmartCardSession.SIGN_OPERATION );
}
/**
* Help signing and decryption operations.
* This helper method assists data signing and decryption because
* the operations are very similar.
*/
private void signDecryptHelper( RSACryptoSystem cryptoSystem,
CryptoTokenPrivateKeyData privateKeyData, byte[] input, int inputOffset,
byte[] output, int outputOffset,String accessReason,int operation )
throws CryptoTokenException, CryptoUnsupportedOperationException
{
SmartCardSession smartCardSession = null;
try {
if( privateKeyData instanceof MyCryptoTokenData ) {
SmartCardID smartCardID = ((MyCryptoTokenData) privateKeyData ).getSmartCardID();
smartCardSession = SmartCardFactory.getSmartCardSession( smartCardID );
if ( smartCardSession instanceof MyCryptoSmartCardSession ) {
MyCryptoSmartCardSession mySmartCardSession =
( MyCryptoSmartCardSession )smartCardSession;
// We must provide the user authentication since we returned true from
//providesUserAuthentication()
// Also, the smart card PIN is required for private key access.
mySmartCardSession.loginPrompt( accessReason, operation );
mySmartCardSession.signDecrypt( cryptoSystem,(MyCryptoTokenData)privateKeyData,
input, inputOffset, output, outputOffset );
return;
}
}
throw new RuntimeException();
}
catch ( SmartCardSessionClosedException e ) {
throw new CryptoTokenCancelException( e.toString() );
}
catch ( SmartCardCancelException e ) {
throw new CryptoTokenCancelException( e.toString() );
}
catch( SmartCardRemovedException e ) {
throw new CryptoTokenCancelException( e.toString() );
}
catch ( SmartCardException e ) {
throw new CryptoTokenException( e.toString() );
}
finally {
if ( smartCardSession != null ) {
smartCardSession.close();
}
}
}
}
存储私有键文件位置
你可以在智能卡的多个位置存储私有键文件.下面的实例描述如何使用一个CryptoTokenPrivateKeyData接口的实现存储智能卡私有键文件的位置.
代码实例
例: MyCryptoTokenData.java
/**
* MyCryptoTokenData.java
* Copyright (C) 2001-2005 Research In Motion Limited. All rights reserved.
*/
package com.rim.samples.device.smartcard;
import net.rim.device.api.crypto.*;
import net.rim.device.api.smartcard.*;
import net.rim.device.api.util.*;
/**
* This class stores the location of the private key file on the smart card.
*
*/
final class MyCryptoTokenData implements CryptoTokenPrivateKeyData, Persistable
{
/**
*Smart card containing the private key.
*/
private SmartCardID _id;
/**
* Location of the private key file on the smart card.
*/
private byte _file;
/**
* Constructs a new MyCryptoTokenData object
*
*@param id ID of the smart card containing the private key file
*@param file Location of the private key file.
*/
public MyCryptoTokenData( SmartCardID id, byte file )
{
_id = id;
_file = file;
}
/**
* Retrieve the ID of the key file containing the private key file.
*
*@return ID of the smart card.
*/
public SmartCardID getSmartCardID()
{
return _id;
}
/**
* Retrieve the location of the private key file on the smart card.
*
* @return Location of the private key file.
*/
public byte getFile()
{
return _file;
}
}
Last Updated:2007年2月5日