/*
*
*
* Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*/
package com.sun.satsa.pki;
import com.sun.midp.io.j2me.apdu.APDUManager;
import com.sun.midp.io.j2me.apdu.Handle;
import com.sun.midp.security.SecurityToken;
import com.sun.satsa.acl.ACLPermissions;
import com.sun.satsa.acl.PINAttributes;
import com.sun.satsa.acl.PINEntryDialog;
import com.sun.satsa.util.*;
import com.sun.midp.i18n.Resource;
import com.sun.midp.i18n.ResourceConstants;
import javax.microedition.pki.UserCredentialManager;
import javax.microedition.pki.UserCredentialManagerException;
import javax.microedition.securityservice.CMSMessageSignatureService;
import javax.microedition.securityservice.CMSMessageSignatureServiceException;
import java.io.IOException;
import java.util.Vector;
import java.util.Calendar;
import java.util.TimeZone;
/**
* This class provides interface to WIM card application.
*/
class WIMApplication {
/** Operation result constant (skip this SE and try next). */
static final int SKIP = 0;
/** Operation result constant. */
static final int SUCCESS = 1;
/** Operation result constant. */
static final int CANCEL = 2;
/** Operation result constant. */
static final int ERROR = 3;
/**
* This class has a different security domain than the MIDlet
* suite */
private SecurityToken securityToken;
/** INS byte for APDU command. */
private static final byte INS_VERIFY = (byte) 0x20;
/** INS byte for APDU command. */
private static final byte INS_MSE = (byte) 0x22;
/** INS byte for APDU command. */
private static final byte INS_PSO = (byte) 0x2a;
/** INS byte for command APDU. */
static final byte INS_NEW = (byte) 0xBC;
/** PIN status constant. */
private static final int PIN_BLOCKED = 0;
/** PIN status constant. */
private static final int PIN_DISABLED = 1;
/** PIN status constant. */
private static final int PIN_REQUIRED = 2;
/** PIN status constant. */
private static final int PIN_CANCELLED = 3;
/** ODF path. */
private static final short ODFPath = 0x5031;
/** EF(TokenInfo) path. */
private static final short TokenInfoPath = 0x5032;
/** EF(UnusedSpace) path. */
private static final short UnusedSpace = 0x5033;
/** ODF entry tag. */
private static final int PRIVATE_KEYS_TAG = 0xa0;
/** ODF entry tag. */
private static final int PUBLIC_KEYS1_TAG = 0xa1;
/** ODF entry tag. */
private static final int PUBLIC_KEYS2_TAG = 0xa2;
/** ODF entry tag. */
private static final int USEFUL_CERTIFICATES_TAG = 0xa4;
/** ODF entry tag. */
private static final int TRUSTED_CERTIFICATES_TAG = 0xa5;
/** ODF entry tag. */
private static final int USER_CERTIFICATES_TAG = 0xa6;
/** ODF entry tag. */
private static final int PINS_TAG = 0xa8;
/** APDUs that must be used for WIM application selection. */
private static final byte[][] selectAPDUs =
{{0, (byte) 0xa4, 4, 0, 12, (byte) 0xA0, 0, 0, 0, 0x63,
0x50, 0x4B, 0x43, 0x53, 0x2D, 0x31, 0x35, 0x7f},
{0, (byte) 0xa4, 4, 0, 12, (byte) 0xA0, 0, 0, 0, 0x63,
0x57, 0x41, 0x50, 0x2D, 0x57, 0x49, 0x4D, 0x7f}};
/** Binary representation of WIM_GENERIC_RSA SE OID, 2.23.43.1.1.2 */
private static final byte[] WIM_GENERIC_RSA_OID =
{0x67, 0x2B, 0x01, 0x01, 0x02};
/** Binary representation of WAP_WSG_WIMPATH OID, 2.23.43.1.3 */
private static final byte[] WAP_WSG_WIMPATH =
{0x67, 0x2B, 0x01, 0x03};
/** RSA digest header. */
private static final byte[] DigestInfoHeader = {
0x30, 0x21, // DigestInfo ::= SEQUENCE {
0x30, 0x09, // AlgorithmIdentifier ::= SEQUENCE {
0x06, 0x05, // id-SHA1 OBJECT IDENTIFIER ::=
0x2b, 0x0e, 0x03, 0x02, 0x1a, // { 1 3 14 3 2 26 } ,
0x05, 0x00, // parameters NULL } ,
0x04, 0x14}; // digest OCTET STRING
/** Connection object. */
private Connection apdu;
/** File system object. */
private WimFileSystem files;
/** Identifier of WIM_GENERIC_RSA SE */
private int WIM_GENERIC_RSA_ID;
/** Identifier of RSA algorithm in EF(TokenInfo) and PrKDFs. */
private int RSA_ALGORITHM_ID;
/** If true this WIM application doesn't allow to modify data. */
private boolean readOnly;
/** Serial number of this WIM card. */
private String serialNumber;
/** This vector contains parsed objects from DF(ODF). */
private Vector ODF;
/** Private keys. */
private PrivateKey[] PrKeys;
/** Public keys. */
private PublicKey[] PuKeys;
/** PINs. */
private PINAttributes[] PINs;
/** Certificates. */
private Certificate[] Certificates;
/** Vector contains identifiers of existing certificates. */
private Vector certificateIDs;
/** This vector contains objects loaded from directory file. */
private Vector loaderObjects;
/** This vector contains locations of loaded objects. */
private Vector loaderLocations;
/**
* This vector contains locations of free space in directory
* files. */
private Vector loaderFreeBlocks;
/** Full path to EF(UnusedSpace) file. */
private short[] UnusedSpacePath;
/**
* Creates connection with WIM application (WIM spec, 11.3.3) on
* card in specified slot. Doesn't throw exceptions.
* @param token security token
* @param slotNum the slot number
* @param securityElementID identifies the security element
* @param readOnly if true WIM data can be protected
* @return WIMApplication object or null.
*/
public static WIMApplication getInstance(SecurityToken token,
int slotNum, String securityElementID, boolean readOnly) {
for (int i = 0; i < selectAPDUs.length; i++) {
Handle h;
APDUManager.initACL(slotNum, token);
try {
h = APDUManager.selectApplication(
selectAPDUs[i], (byte) slotNum, token);
} catch (IOException e) {
continue;
}
WIMApplication w = new WIMApplication(h);
if (w.init(securityElementID) &&
(readOnly || (! w.readOnly))) {
return w;
}
w.done();
}
return null;
}
/**
* Constructs a new WIMApplication object.
* @param h the APDU connection handle
*/
private WIMApplication(Handle h) {
this.apdu = new Connection(h);
files = new WimFileSystem(apdu);
securityToken = h.token;
}
/**
* Reads configuration (EF(TokenInfo) file).
* Doesn't throw exceptions.
* @param securityElementID identifies the security element
* @return true if successful
*/
private boolean init(String securityElementID) {
try {
if (! readTokenInfo(securityElementID)) {
return false;
}
readODF();
loadPINs();
UnusedSpacePath = files.makePath(TLV.createOctetString(
Utils.shortToBytes(UnusedSpace)));
return true;
} catch (IOException te) {
done();
}
return false;
}
/**
* Safely closes the connection.
*/
public void done() {
if (apdu != null) {
apdu.done();
}
apdu = null;
}
/**
* Reads and parses EF(TokenInfo).
* @param securityElementID identifies the security element
* specified by user
* @return true if successful and WIM application is found
* @throws IOException if the file is not found or there is some
* other IO error
* @throws RuntimeException if there is an error parsing the file
* (e.g. its non-WIM PKCS#15 application and some mandatory for WIM
* fields are absent)
*/
private boolean readTokenInfo(String securityElementID)
throws IOException, TLVException {
files.select(TokenInfoPath);
TLV t = new TLV(files.readFile(), 0);
/*
* PKCS15TokenInfo ::= SEQUENCE {
* version INTEGER {v1(0)} (v1,...),
* serialNumber OCTET STRING,
* -manufacturerID PKCS15Label OPTIONAL,
* +label [0] PKCS15Label OPTIONAL,
* +tokenflags PKCS15TokenFlags,
* +seInfo SEQUENCE OF PKCS15SecurityEnvironmentInfo
* OPTIONAL,
* -recordInfo [1] PKCS15RecordInfo OPTIONAL,
* +supportedAlgorithms [2]SEQUENCE OF PKCS15AlgorithmInfo
* OPTIONAL,
* ... -- For future extensions
* }
*/
t = t.child; // version
// it must be integer and it must be 0 (version 1)
if (t.getInteger() != 0) {
return false;
}
t = t.next; // serial number
if (t.type != TLV.OCTETSTR_TYPE) {
return false;
}
serialNumber = Utils.hexNumber(t.data, t.valueOffset, t.length);
// skip optional manufacturerID
t = t.next.skipOptional(TLV.UTF8STR_TYPE);
// it must be label
if (t.type != 0x80) {
return false;
}
String label = t.getUTF8();
if (! (label.startsWith("WIM 1.01") &&
(label.length() == 8 || label.charAt(8) == ' '))) {
return false;
}
if (securityElementID != null &&
label.indexOf(securityElementID) == -1) {
return false;
}
t = t.next; // Token flags. Check if this card is read-only.
readOnly = t.checkFlag(0);
t = t.next; // seInfo
// check if WIM_GENERIC_RSA SE is supported
WIM_GENERIC_RSA_ID = -1;
TLV v = t.child;
while (v != null) {
if (v.child.next.valueEquals(WIM_GENERIC_RSA_OID)) {
WIM_GENERIC_RSA_ID = v.child.getInteger();
break;
}
v = v.next;
}
if (WIM_GENERIC_RSA_ID == -1) {
return false;
}
// skip otional recordInfo
t = t.next.skipOptional(0xa1).child;
// check if RSA signature is supported
boolean supportsSignature = false;
while (t != null) {
TLV m = t.child;
if (m.next.getInteger() == 0) { // 0 - RSA
RSA_ALGORITHM_ID = m.getInteger();
supportsSignature = m.next.next.next.checkFlag(1);
break;
}
t = t.next;
}
return supportsSignature;
}
/**
* Reads ODF and sets WIM root directory if necessary.
* @throws IOException if I/O error occurs
*/
private void readODF() throws IOException, TLVException {
ODF = new Vector();
resetLoader(ODF, null, null);
parseDF(new short[] {ODFPath});
for (int i = 0; i < ODF.size(); i++) {
TLV t = (TLV) ODF.elementAt(i);
if (t.type != 0xa7) {
continue;
}
t = t.child;
if (t.type != 0xa0) { // not [0]SEQUENCE OF ObjectType
continue;
}
t = t.child.child; // 1st of SEQUENCE OF PKCS15Data
while (t != null) {
if (t.type != TLV.SEQUENCE_TYPE) { // not opaqueDO
t = t.next;
continue;
}
TLV m = t.child.next.child.skipOptional(TLV.UTF8STR_TYPE);
if (m.type != TLV.OID_TYPE ||
! m.valueEquals(WAP_WSG_WIMPATH)) {
t = t.next;
continue;
}
m = t.child.next.next.child;
short[] root = new short[m.length / 2];
for (int j = 0; j < root.length; j++) {
root[j] = Utils.getShort(m.data,
m.valueOffset + j * 2);
}
files.setRoot(root);
break;
}
}
}
/**
* Initialises object loader.
* @param objects vector for loaded objects or null
* @param locations vector for object locations or null
* @param freeBlocks vector for unused block locations or null
*/
private void resetLoader(Vector objects,
Vector locations,
Vector freeBlocks) {
loaderObjects = objects;
loaderLocations = locations;
loaderFreeBlocks = freeBlocks;
}
/**
* Finds all the files for specified type, reads and parses them.
* @param tag tag of ODF entry for this type of objects
* @throws IOException if IO error occurs
*/
private void loadObjects(int tag) throws IOException, TLVException {
for (int i = 0; i < ODF.size(); i++) {
TLV t = (TLV) ODF.elementAt(i);
if (t.type == tag) {
parseDF(files.makePath(t.child.child));
}
}
}
/**
* Parses directory file. Places results into vectors specified
* <code>resetLoader</code> method.
* @param path path to directory file
* @throws TLVException if parsing error occurs
* @throws IOException if I/O error occurs
*/
private void parseDF(short[] path) throws TLVException, IOException {
files.select(path);
doParseDF(files.readFile(), path,
loaderObjects, loaderLocations, loaderFreeBlocks);
}
/**
* Loads all RSA private keys. Keys are stored in
* <code>PrKeys</code> array.
* @throws TLVException if parsing error occurs
* @throws IOException if I/O error occurs
*/
private void loadPrivateKeys() throws IOException, TLVException {
Vector v = new Vector();
resetLoader(v, null, null);
loadObjects(PRIVATE_KEYS_TAG);
Vector k = new Vector();
for (int i = 0; i < v.size(); i++) {
TLV t = (TLV) v.elementAt(i);
if (t.type != TLV.SEQUENCE_TYPE) { // non-RSA key
continue;
}
PrivateKey key = new PrivateKey();
t = t.child; // commonObjectAttributes
key.label = t.child.getUTF8().trim();
key.authId = t.child.next.next.getId();
t = t.next;
TLV m = t.child;
key.id = m.getValue();
m = m.next;
key.authentication = m.checkFlag(2);
key.nonRepudiation = m.checkFlag(9);
m = m.next.skipOptional(TLV.BOOLEAN_TYPE);
key.keyReference = m.getInteger() & 0xff;
// skip PKCS15CommonPrivateKeyAttributes
t = t.next.skipOptional(0xa0);
t = t.child.child;
key.path = files.makePath(t.child);
t = t.next;
key.modulusLength = t.getInteger();
t = t.next;
if (t != null &&
t.type == TLV.INTEGER_TYPE &&
t.getInteger() != RSA_ALGORITHM_ID) {
continue;
}
k.addElement(key);
}
PrKeys = new PrivateKey[k.size()];
k.copyInto(PrKeys);
}
/**
* Loads all RSA public keys. Stores keys in <code>PuKeys</code>
* array.
* @throws TLVException if parsing error occurs
* @throws IOException if I/O error occurs
*/
private void loadPublicKeys() throws IOException, TLVException {
Vector v = new Vector();
resetLoader(v, null, null);
loadObjects(PUBLIC_KEYS1_TAG);
loadObjects(PUBLIC_KEYS2_TAG);
Vector k = new Vector();
for (int i = 0; i < v.size(); i++) {
TLV t = (TLV) v.elementAt(i);
if (t.type != TLV.SEQUENCE_TYPE) { // non-RSA key
continue;
}
PublicKey key = new PublicKey();
t = t.child; // commonObjectAttributes
t = t.next; // CommonKeyAttributes
key.id = t.child.getValue();
TLV m = t.child.next.next;
if (m.type != TLV.BOOLEAN_TYPE ||
m.data[m.valueOffset] != 0) {
continue; // native, useless for CSR generation
}
// skip PKCS15CommonPublicKeyAttributes
t = t.next.skipOptional(0xa0);
key.body = files.pathToLocation(t.child.child);
k.addElement(key);
}
PuKeys = new PublicKey[k.size()];
k.copyInto(PuKeys);
}
/**
* Loads PIN objects and places them into <code>PINs</code> array.
* @throws TLVException if parsing error occurs
* @throws IOException if I/O error occurs
*/
private void loadPINs() throws IOException, TLVException {
Vector v = new Vector();
resetLoader(v, null, null);
loadObjects(PINS_TAG);
Vector k = new Vector();
for (int i = 0; i < v.size(); i++) {
TLV t = (TLV) v.elementAt(i);
if (t.type != TLV.SEQUENCE_TYPE) { // not a PIN object
continue;
}
PINAttributes pin = new PINAttributes();
k.addElement(pin);
t = t.child; // commonObjectAttributes
pin.label = t.child.getUTF8().trim();
t = t.next; // CommonAuthenticationObjectAttributes
pin.id = t.child.getId();
t = t.next.child.child; // PinAttributes.pinFlags
if (t.checkFlag(0)) {
pin.pinFlags = PINAttributes.FLAG_CASE_SENSITIVE;
}
if (t.checkFlag(5)) {
pin.pinFlags = PINAttributes.FLAG_NEEDS_PADDING;
}
t = t.next;
pin.pinType = t.getEnumerated();
t = t.next;
pin.minLength = t.getInteger();
t = t.next;
pin.storedLength = t.getInteger();
t = t.next;
if (t.type == TLV.INTEGER_TYPE) {
pin.maxLength = t.getInteger();
t = t.next;
} else {
pin.maxLength = pin.storedLength;
}
// this entry is optional, default value is 0
if (t.type == 0x80) {
pin.pinReference = t.getInteger();
t = t.next;
}
pin.padChar = t.getId();
t = t.next.skipOptional(TLV.GEN_TIME_TYPE);
pin.path = files.makePath(t.child);
}
if (k.size() == 0) {
throw new IOException("PINs not found");
}
PINs = new PINAttributes[k.size()];
k.copyInto(PINs);
}
/**
* Loads attributes of X.509 certificates. Places results into
* <code>Certificates</code> array. Places identifiers of all
* certificates into <code>certificateIDs</code> vector.
* @param loadValues if true loads also the certificates
* @param loadTrusted if true load trusted certificates
* @throws TLVException if parsing error occurs
* @throws IOException if I/O error occurs
*/
private void loadCertificates(boolean loadValues,
boolean loadTrusted)
throws IOException, TLVException {
Vector objects = new Vector();
Vector locations = new Vector();
resetLoader(objects, locations, null);
if (loadTrusted) {
loadObjects(TRUSTED_CERTIFICATES_TAG);
}
loadObjects(USEFUL_CERTIFICATES_TAG);
loadObjects(USER_CERTIFICATES_TAG);
Vector k = new Vector();
certificateIDs = new Vector();
for (int i = 0; i < objects.size(); i++) {
TLV t = (TLV) objects.elementAt(i);
// is it x.509 certificate?
if (t.type != TLV.SEQUENCE_TYPE) {
certificateIDs.addElement(t.child.next.child.getValue());
continue;
}
Certificate cert = new Certificate();
k.addElement(cert);
t = t.child; // commonObjectAttributes
cert.label = t.child.getUTF8().trim();
t = t.next;
cert.id = t.child.getValue();
certificateIDs.addElement(cert.id);
TLV v = t.child.next;
if (v != null) {
v = v.skipOptional(TLV.BOOLEAN_TYPE);
if (v != null) {
cert.requestId = v.getValue();
}
}
t = t.next.child.child;
cert.body = files.pathToLocation(t);
if (loadValues) {
cert.cert = files.loadObject(cert.body);
}
cert.header = (Location) locations.elementAt(i);
}
Certificates = new Certificate[k.size()];
k.copyInto(Certificates);
}
/**
* Verifies PIN status.
* @param pin object containing PIN attributes
* @return PIN status.
*/
private int getPINStatus(PINAttributes pin) {
try {
files.select(pin.path);
apdu.resetCommand().
sendCommand(INS_VERIFY, pin.pinReference, 0, false);
} catch (IOException e) {
return PIN_BLOCKED;
}
if (apdu.lastSW == 0x9000) {
return PIN_DISABLED;
}
if (apdu.lastSW == 0x6983) {
return PIN_BLOCKED;
}
return PIN_REQUIRED;
}
/**
* Verifies the PIN if necessary.
* @param pin object containing PIN attributes
* @return PIN_DISABLED - if the PIN is verified or not required;
* PIN_BLOCKED - if the PIN is blocked; PIN_CANCELLED - if user
* cancelled PIN entry dialog
*/
private int checkPIN(PINAttributes pin) {
while (true) {
int status = getPINStatus(pin);
if (status == PIN_DISABLED) {
return PIN_DISABLED;
}
if (status == PIN_BLOCKED) {
try {
MessageDialog
.showMessage(securityToken,
Resource.getString(ResourceConstants
.ERROR),
Resource
.getString(ResourceConstants
.JSR177_WIM_PIN_BLOCKED) + ":\n" +
pin.label,
false);
} catch (InterruptedException e) {}
return PIN_BLOCKED;
}
// verification is required
PINEntryDialog dialog;
try {
dialog = new PINEntryDialog(securityToken,
ACLPermissions.CMD_VERIFY,
pin, null);
} catch (InterruptedException e) {
return PIN_CANCELLED;
}
dialog.waitForAnswer();
Object[] pins = dialog.getPINs();
if (pins == null) {
return PIN_CANCELLED;
}
boolean ok = true;
try {
byte[] tmp = (byte[]) pins[0];
apdu.resetCommand().
putBytes(tmp, 0, tmp.length).
sendCommand(INS_VERIFY, pin.pinReference, 127, false);
} catch (IOException e) {
ok = false;
}
if (ok & (apdu.lastSW == 0x9000)) {
return PIN_DISABLED;
}
try {
MessageDialog
.showMessage(securityToken,
Resource
.getString(ResourceConstants
.ERROR),
Resource
.getString(ResourceConstants
.JSR177_WIM_PIN_NOT_VERIFIED),
false);
} catch (InterruptedException e) {}
}
}
/**
* Generates CSR. See UserCredentialManager.generateCSR for details.
* The calling method must load a vector that contains IDs of keys
* for which CSRs were generated earlier and save it after successful
* CSR generation.
* @param nameInfo certificate subject name
* @param keyLen key length
* @param keyUsage key usage
* @param forceKeyGen if set to true a new key must be generated
* @param keyIDs IDs of keys for which CSRs were generated earlier.
* If the new CSR is generated, the key ID is added into this vector.
* @return the new CSR or null if operation cancelled
* @throws UserCredentialManagerException if key is not found
* @throws CMSMessageSignatureServiceException if CSR generation
* failed
* @throws SecurityException if a PIN is blocked due to an excessive
* number of incorrect PIN entries
*/
public byte[] generateCSR(String nameInfo, int keyLen, int keyUsage,
boolean forceKeyGen, Vector keyIDs)
throws UserCredentialManagerException,
CMSMessageSignatureServiceException {
// check the name
if (nameInfo == null) {
nameInfo = "CN=" + serialNumber;
}
TLV name;
try {
name = new TLV(RFC2253Name.toDER(nameInfo), 0);
} catch (TLVException e) {
throw new IllegalArgumentException("Invalid name");
}
try {
if (MessageDialog.showMessage(securityToken,
Resource.getString(ResourceConstants.AMS_CONFIRMATION),
Resource.getString(ResourceConstants.
JSR177_CERTIFICATE_GENERATED) +
"\n\n" +
// "Name: "
Resource.getString(ResourceConstants.
JSR177_CERTIFICATE_SUBJECT) + ": " +
nameInfo +
"\n\n" +
// "Key usage: "
Resource.getString(ResourceConstants.
JSR177_CERTIFICATE_KEYUSAGE) + ": " +
((keyUsage ==
UserCredentialManager.KEY_USAGE_AUTHENTICATION) ?
// "authentication"
Resource.getString(ResourceConstants.
JSR177_CERTIFICATE_KEYUSAGE_AUTH) :
// "non-repudiation"
Resource.getString(ResourceConstants.
JSR177_CERTIFICATE_KEYUSAGE_NR)) +
"\n" +
// "Algorithm: "
Resource.getString(ResourceConstants.
JSR177_CERTIFICATE_ALGORITHM) + ": " +
"RSA" +
"\n" +
// "Key length: "
Resource.getString(ResourceConstants.
JSR177_CERTIFICATE_KEYLENGTH) + ": " +
keyLen,
true) == Dialog.CANCELLED) {
return null;
}
} catch (InterruptedException e) {
return null;
}
int keyId = -1;
if (forceKeyGen) {
try {
keyId = generateKey(keyLen, keyUsage);
} catch (IOException ioe) { // ignored
} catch (InterruptedException ie) { // ignored
}
if (keyId == -1) {
throw new UserCredentialManagerException(
UserCredentialManagerException.SE_NO_KEYGEN);
}
if (keyId == -2) {
throw new UserCredentialManagerException(
UserCredentialManagerException.SE_NO_KEYS);
}
}
// load info about keys
try {
if (keyId != -1) {
loadPINs();
}
loadCertificates(false, true);
loadPrivateKeys();
loadPublicKeys();
} catch (IOException e) {
throw new UserCredentialManagerException(
UserCredentialManagerException.SE_NO_KEYS);
}
// find the 'best' key
PrivateKey key = null;
int keyType = 3; // 0 - no certificate or CSR
// 1 - CSR
// 2 - certificate
// 3 - empty
TLV keyValue = null;
for (int i = 0; i < PrKeys.length; i++) {
if (keyId != -1) {
if (keyId == PrKeys[i].keyReference) {
key = PrKeys[i];
keyValue = getPublicKey(PrKeys[i].id);
break;
}
continue;
}
// check key size
if (PrKeys[i].modulusLength != keyLen) {
continue;
}
// check key usage
if (! (keyUsage ==
UserCredentialManager.KEY_USAGE_AUTHENTICATION ?
PrKeys[i].authentication :
PrKeys[i].nonRepudiation)) {
continue;
}
// check if certificate or CSR for this key exists
int type = 0;
if (getIDIndex(keyIDs, PrKeys[i].id) != -1) {
type = 1;
}
if (getIDIndex(certificateIDs, PrKeys[i].id) != -1) {
type = 2;
}
// is this key is better than the previous?
if (type >= keyType) {
continue;
}
// if PIN doesn't exist or blocked, find another key
PINAttributes pin = getPIN(PrKeys[i].authId);
if (pin == null || getPINStatus(pin) == PIN_BLOCKED) {
continue;
}
// if the public key can't be retrieved, find another key
TLV t = getPublicKey(PrKeys[i].id);
if (t == null) {
continue;
}
// the best key so far
key = PrKeys[i];
keyValue = t;
keyType = type;
}
if (key == null) {
throw new UserCredentialManagerException(
UserCredentialManagerException.SE_NO_KEYS);
}
// the key is found and loaded
// check PIN for signature operation
int pinStatus = checkPIN(getPIN(key.authId));
if (pinStatus == PIN_CANCELLED) {
return null;
}
if (pinStatus == PIN_BLOCKED) {
throw new SecurityException("PIN blocked");
}
if (pinStatus != PIN_DISABLED) {
// IMPL_NOTE: need warning message?
throw new CMSMessageSignatureServiceException(
CMSMessageSignatureServiceException.SE_CRYPTO_FAILURE);
}
// PIN is verified, create the CSR
TLV CRInfo = TLV.createSequence();
CRInfo.setChild(TLV.createInteger(0)).
setNext(name).
setNext(keyValue).
setNext(new TLV(TLV.SET_TYPE).setTag(0xa0)).
setChild(TLV.createSequence()).
setChild(TLV.createOID("1.2.840.113549.1.9.14")).
setNext(new TLV(TLV.SET_TYPE)).
setChild(TLV.createSequence()).
setChild(TLV.createSequence()).
setChild(TLV.createOID("2.5.29.15")).
setNext(new TLV(TLV.BOOLEAN_TYPE, new byte[] {(byte) 255})).
setNext(new TLV(TLV.OCTETSTR_TYPE,
keyUsage == UserCredentialManager.KEY_USAGE_AUTHENTICATION ?
new byte[] {3, 2, 7, (byte) 0x80} :
new byte[] {3, 2, 6, 0x40}));
byte[] sign;
try {
sign = signData(key, CRInfo.getDERData());
} catch (IOException e) {
throw new CMSMessageSignatureServiceException(
CMSMessageSignatureServiceException.SE_CRYPTO_FAILURE);
}
TLV alg = CRInfo.child.next.next.child.copy();
TLV OID = TLV.createOID("1.2.840.113549.1.1.5");
TLV params = alg.child.next;
alg = TLV.createSequence();
alg.setChild(OID).setNext(params);
TLV request = TLV.createSequence();
request.setChild(CRInfo).
setNext(alg).
setNext(new TLV(TLV.BITSTRING_TYPE, sign));
// add to the vector of IDs of keys for which CSRs are generated
keyIDs.addElement(key.id);
return request.getDERData();
}
/**
* Generates new key.
* @param keyLen key length
* @param keyUsage key usage
* @return key reference or -1 if the key generation is not
* supported or -2 if key cannot be generated
* @throws IOException if I/O error occurs
* @throws InterruptedException if interrupted
*/
int generateKey(int keyLen, int keyUsage) throws IOException,
InterruptedException {
boolean nonRepudiation = (keyUsage ==
UserCredentialManager.KEY_USAGE_NON_REPUDIATION);
byte[] tmp = apdu.resetCommand().
putByte(nonRepudiation ? 1 : 0).
putShort(keyLen).
sendCommand(INS_NEW, 0x0100, 240, false);
if (apdu.lastSW == 0x9001) {
return -2;
}
if (tmp.length != 6 ||
Utils.getShort(tmp, 0) != 0x1234 ||
Utils.getShort(tmp, 2) != 0x4321) {
return -1;
}
apdu.resetCommand().
putByte(nonRepudiation ? 1 : 0).
putShort(keyLen);
if (nonRepudiation) {
// must create new PIN
String[] pinInfo = MessageDialog.enterNewPIN(securityToken);
if (pinInfo == null) {
return -1;
}
tmp = pinInfo[1].getBytes();
int len = tmp.length;
apdu.putBytes(tmp, 0, len);
while (len++ < 8) {
apdu.putByte(0xff);
}
tmp = Utils.stringToBytes(pinInfo[0]);
len = tmp.length < 32 ? tmp.length : 32;
apdu.putBytes(tmp, 0, len);
while (len++ < 32) {
apdu.putByte(0x20);
}
}
tmp = apdu.sendCommand(INS_NEW, 0x0000, 240, false);
return apdu.lastSW == 0x9000 ? tmp[0] & 0xff : -2;
}
/**
* Sign given data using given key.
* @param key private key
* @param data data to be signed
* @return signature prepended with one zero byte.
* @throws IOException if I/O or crypto error occurs
*/
private byte[] signData(PrivateKey key, byte[] data)
throws IOException {
// calculate SHA-1 digest
byte[] tmp = Utils.getHash(data, 0, data.length);
// MSE - RESTORE
apdu.resetCommand().
sendCommand(INS_MSE, 0xf300 | WIM_GENERIC_RSA_ID, 0, true);
// MSE - SET
apdu.resetCommand().
putByte(0x84). // key reference tag
putByte(0x1). // length
putByte(key.keyReference). // value
putByte(0x81). // private key path tag
putByte(key.path.length * 2); // length
for (int i = 0; i < key.path.length; i++) { // value
apdu.putShort(key.path[i]);
}
apdu.sendCommand(INS_MSE, 0x41b6, 0, true);
// sign the data
tmp = apdu.resetCommand().
putBytes(DigestInfoHeader, 0, DigestInfoHeader.length).
putBytes(tmp, 0, 20).
sendCommand(INS_PSO, 0x9e9a);
byte[] sign = new byte[tmp.length - 1];
System.arraycopy(tmp, 0, sign, 1, tmp.length - 2);
return sign;
}
/**
* Returns index of given identifier in the vector or -1 if not
* found.
* @param IDs vector containing identifiers
* @param id identifier
* @return index of given identifier or -1
*/
private int getIDIndex(Vector IDs, byte[] id) {
for (int j = 0; j < IDs.size(); j++) {
if (Utils.byteMatch((byte[]) IDs.elementAt(j), id)) {
return j;
}
}
return -1;
}
/**
* Returns PIN attributes for given authId.
* @param authId identifier of PIN
* @return PIN attributes or null
*/
private PINAttributes getPIN(int authId) {
for (int i = 0; i < PINs.length; i++) {
if (PINs[i].id == authId) {
return PINs[i];
}
}
return null;
}
/**
* Returns TLV that contains SubjectPublicKeyInfo structure for
* public key.
* @param id key identifier
* @return TLV that contains SubjectPublicKeyInfo structure or null
*/
private TLV getPublicKey(byte[] id) {
// try to obtain the key from certificate
for (int i = 0; i < Certificates.length; i++) {
if (! Utils.byteMatch(Certificates[i].id, id)) {
continue;
}
try {
TLV t = files.loadObject(Certificates[i].body);
return t.child.child.skipOptional(0xa0).next.next.next.
next.next.copy();
} catch (IOException e) {
continue;
}
}
/*
there is no certificate for this private key, try to
read public key
*/
for (int i = 0; i < PuKeys.length; i++) {
if (! Utils.byteMatch(PuKeys[i].id, id)) {
continue;
}
try {
files.select(PuKeys[i].body.path);
if (PuKeys[i].body.length == -1) {
PuKeys[i].body.length = files.getCurrrentFileSize();
}
byte[] tmp = files.readData(1, PuKeys[i].body.length,
PuKeys[i].body.offset);
TLV subjectPKInfo = TLV.createSequence();
TLV alg = TLV.createSequence();
subjectPKInfo.setChild(alg);
alg.setChild(TLV.createOID("1.2.840.113549.1.1.1")).
setNext(new TLV(TLV.NULL_TYPE));
alg.setNext(new TLV(TLV.BITSTRING_TYPE, tmp));
return subjectPKInfo;
} catch (IOException e) {
break;
}
}
return null;
}
/**
* Adds a user certificate or certificate URI to a certificate store.
* See UserCredentialManager.addCredential for details. Calling
* method must remove leading and trailing spaces in label.
* @param label the user friendly name associated with the
* certificate
* @param top chain of certificates from pkiPath
* @param keyIDs vector that contains identifiers of keys for which
* certificates are expected
* @return operation result
* @throws IllegalArgumentException if certificate parsing error
* occurs or label is not unique or user credential exists already
* @throws SecurityException if a PIN is blocked due to an excessive
* number of incorrect PIN entries
*/
public int addCredential(String label, TLV top, Vector keyIDs) {
// load existing certificates
try {
loadPrivateKeys();
loadCertificates(true, true);
} catch (IOException e) {
return SKIP;
}
// put certificates into array
Vector u = new Vector();
while (top != null) {
u.addElement(top);
top = top.next;
}
TLV path[] = new TLV[u.size()];
u.copyInto(path);
// verify certificates encoding and calculate identifier
// the purpose of the check is to ensure that new certificates
// will not cause runtime exceptions, not to verify X.509
// compliance
byte[][] IDs = new byte[path.length][];
try {
TLV Issuer = null;
for (int i = 0; i < path.length; i++) {
TLV t = path[i].child.child.skipOptional(0xa0).next.next;
RFC2253Name.compare(t, t); // issuer
if (Issuer != null && ! RFC2253Name.compare(Issuer, t)) {
throw new IllegalArgumentException();
}
t = t.next; // validity
t.child.getTime(); // notBefore
t.child.next.getTime(); // notAfter
t = t.next; // subject
RFC2253Name.compare(t, t);
Issuer = t;
IDs[i] = getKeyHash(path[i]); // subjectPublicKeyInfo
}
} catch (IOException e) {
throw new IllegalArgumentException("Invalid pkiPath");
} catch (NullPointerException npe) {
throw new IllegalArgumentException("Invalid pkiPath");
}
// check if this WIM contains corresponding private key
if (getPrivateKey(IDs[path.length - 1]) == null) {
return SKIP;
}
// check that the label is unique for this card
if (getCertificate(label) != null) {
throw new IllegalArgumentException(label);
}
// eliminate certificates that already present on the card
for (int i = 0; i < path.length; i++) {
TLV t = path[i].child.child.skipOptional(0xa0);
if (getCertificate(t.next.next, t) != null) {
path[i] = null;
}
}
if (path[path.length - 1] == null) {
throw new IllegalArgumentException("credential exists");
}
// if the 1st certificate is self-signed we don't need to save it
if (path.length > 1 && path[0] != null) {
TLV t = path[0].child.child.skipOptional(0xa0).next.next;
if (RFC2253Name.compare(t, t.next.next)) {
path[0] = null;
}
}
// find place for every certificate and generate CDF records
startUpdate();
Location[] locations;
try {
locations = putObjects(path);
} catch (IOException e) {
return ERROR;
}
if (locations == null) { // if no enough space
return ERROR;
}
Vector headers = new Vector();
int labelNum = 0;
for (int i = 0; i < path.length; i++) {
if (path[i] == null) {
continue;
}
// generate header for certificate
// create unique label if necessary
String t_label = label;
if (i < path.length - 1) {
for (int k = 0; k < 100000; k++) {
t_label = "certificate # " + labelNum++;
if (! label.equals(t_label) &&
getCertificate(t_label) == null) {
break;
}
}
}
// find identifier of the previous certificate
byte[] prevID;
if (i == 0) {
TLV t = path[i].child.child.skipOptional(0xa0).next.next;
Vector v = getCertsBySubject(t);
if (v.size() == 0) {
prevID = new byte[20];
} else {
prevID = ((Certificate) v.elementAt(0)).id;
}
} else {
prevID = IDs[i - 1];
}
TLV commonAttrs = TLV.createSequence();
commonAttrs.
setChild(createLabel(t_label)).
setNext(new TLV(TLV.BITSTRING_TYPE, new byte[2]));
TLV commonCertAttrs = TLV.createSequence();
commonCertAttrs.
setChild(TLV.createOctetString(IDs[i])).
setNext(TLV.createOctetString(prevID));
Location l = locations[i];
TLV x509Attrs = new TLV(0xa1);
x509Attrs.setChild(TLV.createSequence()).
setChild(createPath(l.path, l.offset, l.length));
TLV cdf = TLV.createSequence();
cdf.setChild(commonAttrs).
setNext(commonCertAttrs).
setNext(x509Attrs);
headers.addElement(cdf);
}
// find free space for headers in CDFs
if (! putHeaders(headers)) {
return ERROR;
}
// check PINs
for (int i = 0; i < updatePIN.length; i++) {
if (updatePIN[i]) {
int pinStatus = checkPIN(PINs[i]);
if (pinStatus == PIN_CANCELLED) {
return CANCEL;
}
if (pinStatus == PIN_BLOCKED) {
throw new SecurityException("PIN blocked");
}
if (pinStatus != PIN_DISABLED) {
return ERROR;
}
}
}
// update
try {
doUpdate();
} catch (IOException e) {
return ERROR;
}
// remove key identifier from the list of expected certificates
for (int i = 0; i < keyIDs.size(); i++) {
if (Utils.byteMatch(IDs[path.length - 1],
(byte[]) keyIDs.elementAt(i))) {
keyIDs.removeElementAt(i);
break;
}
}
// typeInfo("AddCredential");
return SUCCESS;
}
/**
* Finds place for new certificates and registers necessary file
* updates.
* @param path array containing certificates
* @return array that contains locations for new certificates.
* @throws TLVException if parsing error occurs
* @throws IOException if I/O error occurs
*/
private Location[] putObjects(TLV[] path) throws IOException,
TLVException {
// find the free space where certificates can be stored
files.select(UnusedSpacePath);
Vector freeSpace = new Vector();
doParseDF(files.readFile(), UnusedSpacePath,
freeSpace, null, null);
Location[] blocks = new Location[freeSpace.size()];
TLV[] records = new TLV[freeSpace.size()];
for (int i = 0; i < freeSpace.size(); i++) {
records[i] = (TLV) freeSpace.elementAt(i);
blocks[i] = files.pathToLocation(records[i].child);
}
Location[] result = new Location[path.length];
for (int j = 0; j < path.length; j++) {
if (path[j] == null) {
continue;
}
byte[] data = path[j].getDERData();
for (int i = 0; i < blocks.length; i++) {
Location block = blocks[i];
if (block.length < data.length) {
continue;
}
block.length -= data.length;
result[j] = new Location(block.path,
block.offset + block.length,
data.length);
update(result[j].path, result[j].offset, data);
// check if PIN is required for this update
TLV t = records[i].child.next;
if (t != null && t.type == TLV.OCTETSTR_TYPE) {
int id;
try {
id = t.getId();
} catch (TLVException e) {
// should never happen
return null;
}
for (int k = 0; k < PINs.length; k++) {
if (PINs[k].id == id) {
updatePIN[k] = true;
}
}
}
// Update length of block
t = records[i].child.child.next.next;
update(UnusedSpacePath, t.valueOffset,
Utils.shortToBytes(block.length));
break;
}
if (result[j] == null) {
return null;
}
}
return result;
}
/**
* Finds place for new certificates directory entries and registers
* all necessary file updates.
* @param headers vector containing new CDF entries
* @return true if successful
*/
private boolean putHeaders(Vector headers) {
Vector holes = null;
for (int i = 0; i < headers.size(); i++) {
try {
if (i == headers.size() - 1) {
holes = new Vector();
resetLoader(null, null, holes);
loadObjects(USEFUL_CERTIFICATES_TAG);
} else
if (i == 0) {
holes = new Vector();
resetLoader(null, null, holes);
loadObjects(USER_CERTIFICATES_TAG);
}
} catch (IOException e) {
// should never happen
return false;
}
TLV header = (TLV) headers.elementAt(i);
byte[] data = header.getDERData();
boolean found = false;
for (int k = 0; k < holes.size(); k++) {
Location l = (Location) holes.elementAt(k);
if (l.length < data.length) {
continue;
}
l.length -= data.length;
update(l.path, l.offset + l.length, data);
// now update the free space after the new record
if (l.length != 0) {
update(l.path, l.offset,
getEmptySpaceHeader(l.length));
found = true;
}
break;
}
if (! found) {
return false;
}
}
return true;
}
/**
* Creates PKCS#15 path.
* @param path card file path
* @param offset offset in the file
* @param length length of data in the file
* @return TLV object that represents PKCS#15 path
*/
private TLV createPath(short[] path, int offset, int length) {
TLV t = TLV.createSequence();
t.setChild(TLV.createOctetString(Utils.shortsToBytes(path))).
setNext(TLV.createInteger(Utils.shortToBytes(offset))).
setNext(TLV.createInteger(Utils.shortToBytes(length)).
setTag(0x80));
return t;
}
/**
* Removes credential.
* @param label the user friendly name associated with the
* certificate.
* @param isn the DER encoded ASN.1 structure that contains the
* certificate issuer and serial number
* @return operation result
* @throws SecurityException if a PIN is blocked due to an excessive
* number of incorrect PIN entries
*/
public int removeCredential(String label, TLV isn) {
// load existing certificates (excluding trusted - can't delete
// them)
try {
loadCertificates(true, false);
} catch (IOException e) {
return SKIP;
}
Certificate cert = getCertificate(isn.child, isn.child.next);
if (cert == null) {
// there is no such certificate
return SKIP;
}
// IMPL_NOTE: should there be a warning?
// if (! cert.label.trim().equals(label)) {}
// find the certificate chain
Vector chain = getChain(cert, null, false);
// this chain can have common certificates with some other
// chains, in this case only part of the chain should be deleted
int count = chain.size();
check:
for (int i = 1; i < chain.size(); i++) {
for (int k = 0; k < Certificates.length; k++) {
if (Certificates[k] != chain.elementAt(i - 1) &&
Certificates[k].isIssuedBy((Certificate)
chain.elementAt(i))) {
count = i;
break check;
}
}
}
try {
if (MessageDialog.showMessage(securityToken,
Resource.getString(ResourceConstants.AMS_CONFIRMATION),
Resource.getString(ResourceConstants
.JSR177_CERTIFICATE_DELETED) +
"\n\n" +
// "Label: "
Resource.getString(ResourceConstants.
JSR177_CERTIFICATE_LABEL) + ": " +
cert.label + "\n\n" +
Certificate.getInfo(cert.cert) + "\n\n",
true) == Dialog.CANCELLED) {
return CANCEL;
}
} catch (InterruptedException e) {
return CANCEL;
}
startUpdate();
try {
doRemove(chain, count);
int pinStatus = checkPIN(PINs[0]);
if (pinStatus == PIN_CANCELLED) {
return CANCEL;
}
if (pinStatus == PIN_BLOCKED) {
throw new SecurityException("PIN blocked");
}
if (pinStatus != PIN_DISABLED) {
return ERROR;
}
doUpdate();
} catch (IOException e) {
return ERROR;
}
// typeInfo("RemoveCredential");
return SUCCESS;
}
/**
* Register all necessary file updates for certificate removal.
* @param chain certificate chain to be removed
* @param count the number of certificates to be removed
* @throws TLVException if parsing error occurs
* @throws IOException if I/O error occurs
*/
private void doRemove(Vector chain, int count) throws IOException,
TLVException {
// Load and parse UnusedSpace.
files.select(UnusedSpacePath);
Vector v_free = new Vector(); // records in UnusedSpace
Vector v_location = new Vector(); // their offsets
Vector v_hole = new Vector(); // empty space in the file
doParseDF(files.readFile(), UnusedSpacePath,
v_free, v_location, v_hole);
TLV[] free = new TLV[v_free.size()];
v_free.copyInto(free);
Location[] block = new Location[free.length];
for (int i = 0; i < free.length; i++) {
block[i] = files.pathToLocation(free[i].child);
}
for (int i = 0; i < count; i++) {
Certificate cert = (Certificate) chain.elementAt(i);
// remove the certificate header from CDF
update(cert.header.path, cert.header.offset,
getEmptySpaceHeader(cert.header.length));
// Now area in data file must be marked as unused.
// try to append/prepend the new block to existing blocks
Location hole = cert.body;
int head = -1;
int tail = -1;
int empty = -1;
for (int k = 0; k < block.length; k++) {
if (block[k] == null ||
! comparePaths(hole.path, block[k].path)) {
continue;
}
if (block[k].length == 0) {
empty = k;
continue;
}
if (doesFollow(block[k], hole)) {
head = k;
continue;
}
if (doesFollow(hole, block[k])) {
tail = k;
}
}
if (head != -1 && tail != -1) {
block[head].length = block[head].length + hole.length +
block[tail].length;
setBlockLength(free[head], block[head].length);
Location newHole = (Location) v_location.elementAt(tail);
deleteBlock(newHole);
v_hole.addElement(newHole);
block[tail] = null;
continue;
}
if (head != -1) {
block[head].length += hole.length;
setBlockLength(free[head], block[head].length);
continue;
}
if (tail != -1) {
block[tail].offset -= hole.length;
block[tail].length += hole.length;
setBlockOffset(free[tail], block[tail].offset);
setBlockLength(free[tail], block[tail].length);
continue;
}
if (empty != -1) {
block[empty].offset = hole.offset;
block[empty].length = hole.length;
setBlockOffset(free[empty], block[empty].offset);
setBlockLength(free[empty], block[empty].length);
continue;
}
// this is a new block, have to allocate new entry
// generate new record with PIN-G authId
TLV n = TLV.createSequence();
TLV t = n.setChild(createPath(hole.path, hole.offset,
hole.length));
t.setNext(TLV.createOctetString(new byte[] {(byte) PINs[0].id}));
byte[] data = n.getDERData();
// find space for new entry
Location l = null;
for (int k = 0; k < v_hole.size(); k++) {
Location loc = (Location) v_hole.elementAt(k);
if (loc.length >= data.length) {
l = loc;
break;
}
}
if (l == null) {
throw new IOException(
"Can't allocate new entry in EF(UnusedSpace)");
}
// update data
update(UnusedSpacePath, l.offset, data);
l.offset += data.length;
l.length -= data.length;
if (l.length != 0) {
update(UnusedSpacePath, l.offset,
getEmptySpaceHeader(l.length));
}
}
}
/**
* Modify offset in record of EF(UnusedSpace).
* @param t TLV object that represents the record
* @param offset the new offset value
*/
private void setBlockOffset(TLV t, int offset) {
t = t.child.child.next;
update(UnusedSpacePath,
t.valueOffset, Utils.shortToBytes(offset));
}
/**
* Modify length of block in record of EF(UnusedSpace).
* @param t TLV object that represents the record
* @param length the new length value
*/
private void setBlockLength(TLV t, int length) {
t = t.child.child.next.next;
update(UnusedSpacePath,
t.valueOffset, Utils.shortToBytes(length));
}
/**
* Mark record of EF(UnusedSpace) as unused.
* @param l location of the record
*/
private void deleteBlock(Location l) {
update(UnusedSpacePath, l.offset, getEmptySpaceHeader(l.length));
}
/**
* Verifies if the second block starts right after the fist.
* @param a1 location of the first block
* @param a2 location of the second block
* @return true if the second block starts right after the fist
*/
private static boolean doesFollow(Location a1, Location a2) {
return (a1.offset + a1.length == a2.offset);
}
/**
* Compares two paths.
* @param path1 the first path
* @param path2 the second path
* @return true if the paths are equal
*/
private static boolean comparePaths(short[] path1, short[] path2) {
if (path1.length != path2.length) {
return false;
}
for (int i = 0; i < path1.length; i++) {
if (path1[i] != path2[i]) {
return false;
}
}
return true;
}
/** Vector contains locations of blocks that must be updated. */
private Vector updateLocations;
/** Vector contains values that must be written. */
private Vector updateData;
/**
* Flags that indicate which PINs must be verified before the
* update.
*/
private boolean[] updatePIN;
/** Initialises the new update. */
private void startUpdate() {
updateLocations = new Vector();
updateData = new Vector();
updatePIN = new boolean[PINs.length];
updatePIN[0] = true;
}
/**
* Registers file modification.
* @param path file path
* @param offset offset in the file
* @param data data to be written
*/
private void update(short[] path, int offset, byte[] data) {
updateLocations.addElement(new Location(path, offset, 0));
updateData.addElement(data);
}
/**
* Performs all the registered updates.
* @throws IOException if I/O error occurs
*/
private void doUpdate() throws IOException {
for (int i = 0; i < updateLocations.size(); i++) {
Location l = (Location) updateLocations.elementAt(i);
files.select(l.path);
byte[] d = (byte[]) updateData.elementAt(i);
files.writeData(d, 0, d.length, l.offset);
}
updateLocations = null;
updateData = null;
}
/**
* Returns private key for given ID.
* @param id key identifier
* @return the key or null if not found
*/
private PrivateKey getPrivateKey(byte[] id) {
for (int i = 0; i < PrKeys.length; i++) {
if (Utils.byteMatch(id, PrKeys[i].id)) {
return PrKeys[i];
}
}
return null;
}
/**
* Returns certificates for given subject.
* @param subject TLV structure that represents RFC 2253 name.
* @return certificates for given subject
*/
private Vector getCertsBySubject(TLV subject) {
Vector v = new Vector();
for (int i = 0; i < Certificates.length; i++) {
if (RFC2253Name.compare(subject,
Certificates[i].getSubject())) {
v.addElement(Certificates[i]);
}
}
return v;
}
/**
* Returns certificate for given label.
* @param label the user friendly certificate label
* @return the certificate object or null if not found
*/
private Certificate getCertificate(String label) {
for (int i = 0; i < Certificates.length; i++) {
if (Certificates[i].label.equals(label)) {
return Certificates[i];
}
}
return null;
}
/**
* Returns certificate for given issuer and serial number.
* @param issuer TLV structure that represents RFC 2253 name.
* @param serialNumber certificate serial number
* @return the certificate object or null if not found
*/
private Certificate getCertificate(TLV issuer, TLV serialNumber) {
for (int i = 0; i < Certificates.length; i++) {
TLV t = Certificates[i].cert.child.child.skipOptional(0xa0);
if (t.match(serialNumber) &&
t.next.next.match(issuer)) {
return Certificates[i];
}
}
return null;
}
/**
* Calculates public key hash for given X.509 certificate.
* @param cert TLV structure that represents X.509 certificate
* @return public key hash
* @throws TLVException if parsing error occurs
*/
private static byte[] getKeyHash(TLV cert) throws TLVException {
TLV t = cert.child.child.skipOptional(0xa0).
next.next.next.next.next.child;
// t is at subjectPublicKeyInfo.algorithm field
byte[] data;
int offset;
int length;
TLV m = t.child;
t = t.next;
if (m.match(TLV.createOID("1.2.840.113549.1.1.1"))) {
// RSA
data = (new TLV(t.data, t.valueOffset + 1).child).getValue();
offset = 0;
length = data.length;
} else
if (m.match(TLV.createOID("1.2.840.10045.2.1"))) {
// ECDSA
data = t.data;
offset = t.valueOffset + 2;
if (t.data[t.valueOffset + 1] == 4) {
// uncompressed form
length = (t.length - 2) / 2;
} else {
// compressed form
length = (t.length - 2);
}
} else {
// PKCS#15 doesn't say anything about this case
// just calculate the hash of subjectPublicKey data
data = t.data;
offset = t.valueOffset + 1;
length = t.length - 1;
}
return Utils.getHash(data, offset, length);
}
/**
* Pads the string to 32 bytes as recommended in WIM and returns TLV
* value that can be used as label.
* @param label label string
* @return TLV object for new label
*/
private static TLV createLabel(String label) {
int len = Utils.stringToBytes(label).length;
while (len < 32) {
label = label + " ";
len++;
}
return TLV.createUTF8String(label);
}
/**
* Creates block header that can be used to mark empty space in
* directory file.
* @param length free block length
* @return block header
*/
private static byte[] getEmptySpaceHeader(int length) {
if (length == 1) {
return new byte[] {(byte) 0xff};
}
if (length < 130) {
return new byte[] {0, (byte) (length - 2)};
}
if (length < 259) {
return new byte[] {0, (byte) 0x81, (byte) (length - 3)};
}
length -= 4;
return new byte[] {0, (byte) 0x82, (byte) (length >> 8),
(byte) length};
}
/**
* Parses EF(DF).
* @param data data to be parsed
* @param path file path
* @param objects method places objects from file into this vector.
* Can be null. Contains values of TLV type
* @param locations method places location of objects into this
* vector. Can be null. Contains values of type Location.
* @param freeBlocks method places locations of free memory in
* EF(DF) into this vector. Can be null. Contains values of
* type Location.
* @throws TLVException if parsing error occurs
*/
private static void doParseDF(byte[] data, short[] path,
Vector objects, Vector locations, Vector freeBlocks)
throws TLVException {
int start = 0;
int current = 0;
while (current < data.length) {
// free space - skip
if (data[current] == (byte) 0xff) {
current++;
continue;
}
// TLV object
TLV t = new TLV(data, current);
// empty one - skip
if (t.type == 0) {
current = t.valueOffset + t.length;
continue;
}
// real object
if (objects != null) {
objects.addElement(t);
}
if (locations != null) {
locations.addElement(new Location(path, current,
t.valueOffset + t.length - current));
}
if (freeBlocks != null && start < current) {
freeBlocks.addElement(
new Location(path, start, current - start));
}
current = t.valueOffset + t.length;
start = current;
}
if (start < current && freeBlocks != null) {
freeBlocks.addElement(
new Location(path, start, current - start));
}
}
/**
* Generates a signature.
* @param nonRepudiation if true, the non-repudiation key must be
* used, otherwise - authentication key
* @param data the data to be signed
* @param options signature content options
* @param caNames array that contains parsed names of certificate
* authorities
* @return the DER encoded signature, null if the signature
* generation was cancelled by the user before completion
* @throws CMSMessageSignatureServiceException if an error occurs
* during signature generation
*/
public byte[] generateSignature(boolean nonRepudiation, byte[] data,
int options, TLV[] caNames)
throws CMSMessageSignatureServiceException {
// load existing certificates
try {
loadPrivateKeys();
loadCertificates(true, true);
} catch (IOException e) {
throw new CMSMessageSignatureServiceException(
CMSMessageSignatureServiceException.SE_FAILURE);
}
// find certificate chains
Vector chains = getChains(nonRepudiation, caNames);
if (chains.size() == 0) {
throw new CMSMessageSignatureServiceException(
CMSMessageSignatureServiceException.CRYPTO_NO_CERTIFICATE);
}
// select certificate
Vector chain = selectChain(chains);
if (chain == null) {
return null;
}
Certificate cert = (Certificate) chain.elementAt(0);
PrivateKey key = getPrivateKey(cert.id);
PINAttributes pin = getPIN(key.authId);
int pinStatus = checkPIN(pin);
if (pinStatus == PIN_BLOCKED) {
throw new SecurityException();
}
if (pinStatus == PIN_CANCELLED) {
return null;
}
TLV signedAttrs = new TLV(TLV.SET_TYPE);
TLV t = signedAttrs.setChild(TLV.createSequence());
t.setChild(TLV.createOID("1.2.840.113549.1.9.3")). // ContentType
setNext(new TLV(TLV.SET_TYPE)).
setChild(TLV.createOID("1.2.840.113549.1.7.1")); // data
t.next = TLV.createSequence();
t = t.next;
Calendar calendar = Calendar.getInstance();
calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
t.setChild(TLV.createOID("1.2.840.113549.1.9.5")). // signingTime
setNext(new TLV(TLV.SET_TYPE)).
setChild(TLV.createUTCTime(calendar));
t.next = TLV.createSequence();
t = t.next;
t.setChild(TLV.createOID("1.2.840.113549.1.9.4")). // messageDigest
setNext(new TLV(TLV.SET_TYPE)).
setChild(TLV.createOctetString(Utils.getHash(data, 0, data.length)));
// generate signature
byte[] signature;
try {
signature = signData(key, signedAttrs.getDERData());
} catch (IOException e) {
throw new CMSMessageSignatureServiceException(
CMSMessageSignatureServiceException.CRYPTO_FAILURE);
}
// format the signature
/*
* ContentInfo ::= SEQUENCE {
* contentType ContentType,
* content [0] EXPLICIT ANY DEFINED BY contentType }
*
* ContentType ::= OBJECT IDENTIFIER
*
* id-signedData OBJECT IDENTIFIER ::= { iso(1) member-body(2)
* us(840) rsadsi(113549) pkcs(1) pkcs7(7) 2 }
*/
TLV ContentInfo = TLV.createSequence();
t = ContentInfo.
setChild(TLV.createOID("1.2.840.113549.1.7.2")).
setNext(new TLV(0xa0)).
setChild(TLV.createSequence());
/*
* t - SignedData
* SignedData ::= SEQUENCE {
* version CMSVersion,
* digestAlgorithms DigestAlgorithmIdentifiers,
* encapContentInfo EncapsulatedContentInfo,
* certificates [0] IMPLICIT CertificateSet OPTIONAL,
* - crls [1] IMPLICIT CertificateRevocationLists OPTIONAL,
* signerInfos SignerInfos }
*
* DigestAlgorithmIdentifiers ::= SET OF
* DigestAlgorithmIdentifier
*/
t = t.
setChild(TLV.createInteger(1)). // version
setNext(new TLV(TLV.SET_TYPE)); // digestAlgorithms
TLV SHAAlgId = TLV.createSequence();
SHAAlgId.setChild(TLV.createOID("1.3.14.3.2.26")); // SHA-1
t.setChild(SHAAlgId.copy());
/*
* EncapsulatedContentInfo ::= SEQUENCE {
* eContentType ContentType,
* eContent [0] EXPLICIT OCTET STRING OPTIONAL }
*
* ContentType ::= OBJECT IDENTIFIER
*
* id-data OBJECT IDENTIFIER ::= { iso(1) member-body(2)
* us(840) rsadsi(113549) pkcs(1) pkcs7(7) 1 }
*/
t = t.setNext(TLV.createSequence());
TLV m = t.setChild(TLV.createOID("1.2.840.113549.1.7.1"));
if ((options &
CMSMessageSignatureService.SIG_INCLUDE_CONTENT) != 0) {
m.setNext(new TLV(0xa0)).
setChild(TLV.createOctetString(data));
}
/*
* certificates [0] IMPLICIT CertificateSet OPTIONAL,
*
* CertificateSet ::= SET OF CertificateChoices
*
* CertificateChoices ::= CHOICE {
* certificate Certificate, -- See X.509
* extendedCertificate [0] IMPLICIT ExtendedCertificate,
* -- Obsolete
* attrCert [1] IMPLICIT AttributeCertificate }
*
*/
if ((options &
CMSMessageSignatureService.SIG_INCLUDE_CERTIFICATE) != 0) {
t = t.setNext(new TLV(0xa0));
TLV n = t.setChild(cert.cert);
for (int i = 1; i < chain.size(); i++) {
n = n.setNext(((Certificate) chain.elementAt(i)).cert);
}
}
/*
* signerInfos SignerInfos }
* SignerInfos ::= SET OF SignerInfo
*
* SignerInfo ::= SEQUENCE {
* version CMSVersion,
* sid SignerIdentifier,
* digestAlgorithm DigestAlgorithmIdentifier,
* signedAttrs [0] IMPLICIT SignedAttributes OPTIONAL,
* signatureAlgorithm SignatureAlgorithmIdentifier,
* signature SignatureValue,
* - unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL }
*
* SignerIdentifier ::= CHOICE {
* issuerAndSerialNumber IssuerAndSerialNumber,
* - subjectKeyIdentifier [0] SubjectKeyIdentifier }
*
* IssuerAndSerialNumber ::= SEQUENCE {
* issuer Name,
* serialNumber CertificateSerialNumber }
*
*/
t = t.setNext(new TLV(TLV.SET_TYPE)).
setChild(TLV.createSequence()).
setChild(TLV.createInteger(1)).
setNext(cert.getIssuerAndSerialNumber()).
setNext(SHAAlgId). // SHA-1
setNext(signedAttrs).setTag(0xa0).
setNext(cert.getKeyAlgorithmID()).
setNext(new TLV(TLV.OCTETSTR_TYPE, signature, 1));
return ContentInfo.getDERData();
}
/**
* Finds certificate chains for specified operation.
* @param nonRepudiation if true, the non-repudiation key must be
* used, otherwise - authentication key
* @param caNames array that contains parsed names of certificate
* authorities
* @return vector of certificate chains
*/
private Vector getChains(boolean nonRepudiation, TLV[] caNames) {
// find the chains
Vector chains = new Vector();
for (int i = 0; i < Certificates.length; i++) {
PrivateKey key = getPrivateKey(Certificates[i].id);
if ((key != null) && (nonRepudiation ? key.nonRepudiation :
key.authentication)) {
Vector chain = getChain(Certificates[i], caNames, true);
if (chain != null) {
chains.addElement(chain);
}
}
}
return chains;
}
/**
* Builds certificate chain for given certificate.
* @param cert user certificate
* @param caNames array that contains parsed names of certificate
* authorities
* @param checkValidity check validity of certificates
* @return vector containing certificate chain or null
*/
private Vector getChain(Certificate cert, TLV[] caNames,
boolean checkValidity) {
Vector chain = new Vector();
while (true) {
if (checkValidity && cert.isExpired()) {
// the certificate is expired, can't build the chain
return null;
}
chain.addElement(cert);
TLV issuer = cert.getIssuer();
// if this is the certificate issued by one of the CAs in
// the list then the chain is complete
if (caNames != null) {
// check if we need any additional certificates
for (int i = 0; i < caNames.length; i++) {
if (RFC2253Name.compare(issuer, caNames[i])) {
return chain;
}
}
}
// search for the issuer certificate
boolean found = false;
for (int i = 0; i < Certificates.length; i++) {
Certificate next = Certificates[i];
found = cert.isIssuedBy(next) &&
! chain.contains(next);
if (found) {
cert = next;
break;
}
}
if (! found) {
// issuer certificate was not found
// if caNames is null, the chain is good enough,
// otherwise the chain is not found
return caNames == null ? chain : null;
}
}
}
/**
* Allows user to select certificate or cancel signature operation.
* @param chains array of certifcate chains
* @return user selected certificate chain
*/
private Vector selectChain(Vector chains) {
String[] labels = new String[chains.size()];
for (int i = 0; i < chains.size(); i++) {
labels[i] = ((Certificate)
((Vector) chains.elementAt(i)).elementAt(0)).label;
}
int chainIndex = -1;
try {
if (chains.size() == 1) {
// if only one chain is found show certificate label to
// the user
if (MessageDialog.showMessage(securityToken,
Resource.getString(ResourceConstants
.AMS_CONFIRMATION),
Resource.getString(ResourceConstants
.JSR177_CERTIFICATE_USED) +
labels[0], true) != Dialog.CANCELLED) {
chainIndex = 0;
}
} else {
// if more than one chain is found ask user to choose
// one of them
chainIndex = MessageDialog.chooseItem(securityToken,
Resource.getString(ResourceConstants
.JSR177_SELECT_CERTIFICATE),
Resource.getString(ResourceConstants
.JSR177_CERTIFICATES_AVAILABLE),
labels);
}
} catch (InterruptedException e) {}
return (chainIndex == -1) ? null :
(Vector) chains.elementAt(chainIndex);
}
// IMPL_NOTE: delete after debugging
/**
* Debug output.
* @param Title title text
* /
private void typeInfo(String Title) {
System.out.println("***********************************");
System.out.println("debug " + Title);
System.out.println("***********************************");
try {
files.select(UnusedSpacePath);
Vector v_free = new Vector(); // records in UnusedSpace
Vector v_location = new Vector(); // their offsets
Vector v_hole = new Vector(); // empty space in the file
doParseDF(files.readFile(), UnusedSpacePath,
v_free, v_location, v_hole);
System.out.println("-------------------------------------");
System.out.println("Unused space entries " + v_free.size());
System.out.println();
for (int i = 0; i < v_free.size(); i++) {
((Location) v_location.elementAt(i)).print();
((TLV) v_free.elementAt(i)).print();
System.out.println();
}
System.out.println("-------------------------------------");
System.out.println("Holes in UnusedSpace " + v_hole.size());
System.out.println();
for (int i = 0; i < v_hole.size(); i++) {
((Location) v_hole.elementAt(i)).print();
System.out.println();
}
loadCertificates(true, true);
System.out.println("-------------------------------------");
System.out.println("Certificates " + Certificates.length);
for (int i = 0; i < Certificates.length; i++) {
Certificates[i].print();
}
} catch (Exception e) {
System.out.println("Exception in typeInfo " + e);
}
}
*/
}
|