package sos.passportapplet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.JCSystem;
import javacard.framework.Util;
import javacard.security.KeyBuilder;
import javacard.security.RSAPublicKey;
import javacard.security.Signature;
/**
* Encapsulation class for a card verifiable certificates according to EAC 1.11.
*
* @author Wojciech Mostowski <woj@cs.ru.nl>
*
*/
public class CVCertificate {
private static final byte ROLE_DV_DOMESTIC = (byte)0x80;
private static final byte ROLE_DV_FOREIGN = (byte)0x40;
private static final byte ACCESS_DG3 = 0x01;
private static final byte ACCESS_DG4 = 0x02;
private static final byte CAR_TAG = 0x42;
/**
* Offsets to where the particular data (offsets & lengths) of the current
* certificate is (temporarily) stored in the data array
*/
static final short OFFSET_PUB_KEY_MODULUS_OFFSET = 0;
static final short OFFSET_PUB_KEY_MODULUS_LENGTH = 1;
static final short OFFSET_PUB_KEY_EXPONENT_OFFSET = 2;
static final short OFFSET_PUB_KEY_EXPONENT_LENGTH = 3;
static final short OFFSET_SUB_ID_OFFSET = 4;
static final short OFFSET_SUB_ID_LENGTH = 5;
static final short OFFSET_AUTHORIZATION_OFFSET = 6;
static final short OFFSET_EFF_DATE_OFFSET = 7;
static final short OFFSET_EXP_DATE_OFFSET = 8;
static final short OFFSET_SIGNATURE_OFFSET = 9;
static final short OFFSET_SIGNATURE_LENGTH = 10;
static final short OFFSET_BODY_LENGTH = 11;
/** Different tags to parse */
private static final short TAG_CERT_BODY = 0x7F4E;
private static final short TAG_CERT_VERSION = 0x5F29;
private static final short TAG_AUTH_ID = 0x42;
private static final short TAG_PUB_KEY = 0x7F49;
private static final short TAG_OID = 0x06;
private static final short TAG_MODULUS = 0x81;
private static final short TAG_EXPONENT = 0x82;
private static final short TAG_SUBJECT_ID = 0x5F20;
private static final short TAG_SUBJECT_AUTH = 0x7F4C;
private static final short TAG_AUTHORIZATION = 0x53;
private static final short TAG_EFF_DATE = 0x5F25;
private static final short TAG_EXP_DATE = 0x5F24;
private static final short TAG_SIGNATURE = 0x5F37;
/** The ASN1 OID of the only algorithm our certificates support */
private static final byte[] RSA_SHA1_OID = { 0x04, 0x00,
0x7F, 0x00, 0x07, 0x02, 0x02, 0x02, 0x01, 0x01 };
/** The EAC OID, see EAC 1.11, D.2.1.3 */
private static final byte[] EAC_OID = { 0x04, 0x00,
0x7F, 0x00, 0x07, 0x03, 0x01, 0x02, 0x01 };
short[] data;
Object[] source;
RSAPublicKey currentCertPublicKey;
byte[] currentCertSubjectId;
byte[] effectiveCertAuthorization;
byte[] currentCertEffDate;
byte[] currentCertExpDate;
byte[] accessFlag;
byte[] currentDate = { 0x00, 0x09, 0x00, 0x01, 0x00, 0x01 }; // 2009-01-01
Signature signature;
byte[] currentCertNum;
byte[] cert1HolderReference;
byte[] cert1PublicKeyData;
byte cert1Authorization;
byte[] cert1EffDate;
byte[] cert1ExpDate;
byte[] cert2HolderReference;
byte[] cert2PublicKeyData;
byte cert2Authorization;
byte[] cert2EffDate;
byte[] cert2ExpDate;
byte[] cvcaFileReference;
CVCertificate() {
data = JCSystem.makeTransientShortArray(
(short) (OFFSET_BODY_LENGTH + 1), JCSystem.CLEAR_ON_DESELECT);
effectiveCertAuthorization = JCSystem.makeTransientByteArray((short)1,
JCSystem.CLEAR_ON_DESELECT);
currentCertNum = JCSystem.makeTransientByteArray((short)1,
JCSystem.CLEAR_ON_DESELECT);
accessFlag = JCSystem.makeTransientByteArray((short)1,
JCSystem.CLEAR_ON_DESELECT);
currentCertEffDate = JCSystem.makeTransientByteArray((short)6,
JCSystem.CLEAR_ON_DESELECT);
currentCertExpDate = JCSystem.makeTransientByteArray((short)6,
JCSystem.CLEAR_ON_DESELECT);
source = JCSystem.makeTransientObjectArray((short) 1,
JCSystem.CLEAR_ON_DESELECT);
currentCertSubjectId = JCSystem.makeTransientByteArray((short) 17,
JCSystem.CLEAR_ON_DESELECT);
currentCertPublicKey = (RSAPublicKey) KeyBuilder.buildKey(
KeyBuilder.TYPE_RSA_PUBLIC, KeyBuilder.LENGTH_RSA_1024, false);
signature = Signature.getInstance(Signature.ALG_RSA_SHA_PKCS1, false);
}
/**
* Checks if the provided subject id matches the current one, if any, or selects one of the root ones.
*
* @param data
* byte[] with the subject id
* @param offset
* offset to data
* @param length
* length of the data
* @return true if the current subject match or it was possible to select one of the root ones
*/
boolean selectSubjectId(byte[] data, short offset, short length) {
if(currentCertSubjectId[0] == 0) {
if(cert1HolderReference != null && cert1HolderReference[0] == (byte)length) {
if(Util.arrayCompare(cert1HolderReference, (short)1, data, offset, length) == 0) {
setupCurrentKey(cert1HolderReference, cert1PublicKeyData, cert1Authorization, cert1EffDate, cert1ExpDate);
currentCertNum[0] = 1;
return true;
}
}else if(cert2HolderReference != null && cert2HolderReference[0] == (byte)length) {
if(Util.arrayCompare(cert2HolderReference, (short)1, data, offset, length) == 0) {
setupCurrentKey(cert2HolderReference, cert2PublicKeyData, cert2Authorization, cert2EffDate, cert2ExpDate);
currentCertNum[0] = 2;
return true;
}
}
return false;
}
return length == currentCertSubjectId[0] && Util.arrayCompare(currentCertSubjectId, (short) 1, data, offset,
length) == 0;
}
// Sets up the current certificate data from the certificate contained in one
// of the cvca certificate stored in this object
private void setupCurrentKey(byte[] certHolderReference, byte[] certPublicKeyData, byte certAuthorization, byte[] certEffDate, byte[] certExpDate) {
Util.arrayCopyNonAtomic(certHolderReference, (short)0, currentCertSubjectId, (short)0, (short)certHolderReference.length);
currentCertPublicKey.setExponent(certPublicKeyData, (short)0, (short)3);
currentCertPublicKey.setModulus(certPublicKeyData, (short)3, (short)128);
effectiveCertAuthorization[0] = certAuthorization;
Util.arrayCopyNonAtomic(certEffDate, (short)0, currentCertEffDate, (short)0, (short)6);
Util.arrayCopyNonAtomic(certExpDate, (short)0, currentCertExpDate, (short)0, (short)6);
}
// Sets up the current certificate data from the certificate contained in source and data.
private void setupCurrentKeyFromCurrentCertificate() {
byte[] certData = (byte[])source[0];
short certHolderReferenceOffset = data[OFFSET_SUB_ID_OFFSET];
short certHolderReferenceLength = data[OFFSET_SUB_ID_LENGTH];
short pubKeyExpOffset = data[OFFSET_PUB_KEY_EXPONENT_OFFSET];
short pubKeyExpLength = data[OFFSET_PUB_KEY_EXPONENT_LENGTH];
short pubKeyModOffset = data[OFFSET_PUB_KEY_MODULUS_OFFSET];
short pubKeyModLength = data[OFFSET_PUB_KEY_MODULUS_LENGTH];
short authorizationOffset = data[OFFSET_AUTHORIZATION_OFFSET];
short effDateOffset = data[OFFSET_EFF_DATE_OFFSET];
short expDateOffset = data[OFFSET_EXP_DATE_OFFSET];
Util.arrayCopyNonAtomic(certData, certHolderReferenceOffset, currentCertSubjectId, (short)1, certHolderReferenceLength);
currentCertSubjectId[0] = (byte)certHolderReferenceLength;
currentCertPublicKey.setExponent(certData, pubKeyExpOffset, pubKeyExpLength);
currentCertPublicKey.setModulus(certData, pubKeyModOffset, pubKeyModLength);
effectiveCertAuthorization[0] &= certData[authorizationOffset];
Util.arrayCopyNonAtomic(certData, effDateOffset, currentCertEffDate, (short)0, (short)6);
Util.arrayCopyNonAtomic(certData, expDateOffset, currentCertExpDate, (short)0, (short)6);
}
/**
* Cleans up the current certificate information.
*
*/
void clear() {
for (short i = 0; i < data.length; i++) {
data[i] = 0;
}
Util.arrayFillNonAtomic(effectiveCertAuthorization, (short) 0, (short) 1,
(byte) 0);
Util.arrayFillNonAtomic(currentCertSubjectId, (short) 0, (short) 17,
(byte) 0);
Util.arrayFillNonAtomic(currentCertEffDate, (short) 0, (short) 6,
(byte) 0);
Util.arrayFillNonAtomic(currentCertExpDate, (short) 0, (short) 6,
(byte) 0);
Util.arrayFillNonAtomic(currentCertNum, (short) 0, (short) 1,
(byte) 0);
Util.arrayFillNonAtomic(accessFlag, (short) 0, (short) 1,
(byte) 0);
currentCertPublicKey.clearKey();
source[0] = null;
}
/**
* Verify the current certificate (ie. the data in source) using the current
* state of certificate verification data (publicKey, subject id, etc.) The
* verification procedure is described in EAC 1.11 spec in various places.
*
* @return true if certificate verification succeeds
*/
boolean verify() {
byte[] certData = (byte[])source[0];
short bodyLength =data[OFFSET_BODY_LENGTH];
short sigOffset =data[OFFSET_SIGNATURE_OFFSET];
short sigLength =data[OFFSET_SIGNATURE_LENGTH];
// check the actual signature
signature.init(currentCertPublicKey, Signature.MODE_VERIFY);
signature.update(certData, (short) 0,
bodyLength);
boolean result = signature.verify(certData, bodyLength,
(short) 0, certData, sigOffset, sigLength);
// check dates
result = (compareDate((byte[]) source[0], data[OFFSET_EXP_DATE_OFFSET],
currentDate, (short) 0) > 0)
&& result;
short subjectIdOffset = data[OFFSET_SUB_ID_OFFSET];
short subjectIdLength = data[OFFSET_SUB_ID_LENGTH];
if((cert1HolderReference != null && (byte)subjectIdLength == cert1HolderReference[0] &&
Util.arrayCompare(cert1HolderReference, (short)1, certData, subjectIdOffset, subjectIdLength) == 0)
||
(cert2HolderReference != null && (byte)subjectIdLength == cert2HolderReference[0] &&
Util.arrayCompare(cert2HolderReference, (short)1, certData, subjectIdOffset, subjectIdLength) == 0)){
result = false;
}
if (result) {
boolean preDomestic = (byte)(effectiveCertAuthorization[0] & ROLE_DV_DOMESTIC) == ROLE_DV_DOMESTIC;
setupCurrentKeyFromCurrentCertificate();
boolean bit1 = (byte)(effectiveCertAuthorization[0] & ROLE_DV_DOMESTIC) == ROLE_DV_DOMESTIC;
boolean bit2 = (byte)(effectiveCertAuthorization[0] & ROLE_DV_FOREIGN) == ROLE_DV_FOREIGN;
boolean setTime = bit1 || bit2 || preDomestic;
boolean setCert = bit1 && bit2;
boolean grantAccess = !bit1 && !bit2;
if(setTime && compareDate(currentDate, (short)0, currentCertEffDate, (short)0) >= 0) {
setTime = false;
}
if(setCert || setTime) {
byte num = currentCertNum[0];
byte[] certHolderReference = num == 1 ? cert1HolderReference : cert2HolderReference;
byte[] certPublicKeyData = num == 1 ? cert1PublicKeyData : cert2PublicKeyData;
byte[] certEffDate = num == 1 ? cert1EffDate : cert2EffDate;
byte[] certExpDate = num == 1 ? cert1ExpDate : cert2ExpDate;
JCSystem.beginTransaction();
if(setCert) {
if(num == 1) {
cert1Authorization = effectiveCertAuthorization[0];
}else{
cert2Authorization = effectiveCertAuthorization[0];
}
Util.arrayCopy(currentCertSubjectId, (short)0, certHolderReference, (short)0, (short)17);
Util.arrayCopy(currentCertEffDate, (short)0, certEffDate, (short)0, (short)6);
Util.arrayCopy(currentCertExpDate, (short)0, certExpDate, (short)0, (short)6);
currentCertPublicKey.getExponent(certPublicKeyData, (short)0);
currentCertPublicKey.getModulus(certPublicKeyData, (short)3);
short index = 0;
if(cert1HolderReference != null) {
index = setupCVCA(index, cert1HolderReference);
}
if(cert2HolderReference != null) {
index = setupCVCA(index, cert2HolderReference);
}
while(index < 36) cvcaFileReference[index++] = 0;
}
if(setTime) {
Util.arrayCopy(currentCertEffDate, (short)0, currentDate, (short)0, (short)6);
}
JCSystem.commitTransaction();
}
if(setCert) {
clear();
}
if(grantAccess) {
accessFlag[0] = effectiveCertAuthorization[0];
// FIXME: clear() ?
}
} else {
clear();
}
return result;
}
// Updates the cvcaFile contents with the new CVCA reference
private short setupCVCA(short index, byte[] reference) {
short len = reference[0];
cvcaFileReference[index++] = CAR_TAG;
cvcaFileReference[index++] = (byte)len;
Util.arrayCopy(reference, (short)1, cvcaFileReference, index, len);
index += len;
return index;
}
/**
* Sets the root certificate data stored in this object from the data recoreded in
* <code>source</code> and <code>data</code>. This is only used during applet
* personalisation.
*
* @param num certificate number, 1 or 2.
*/
void setRootCertificate(byte[] in, short num) {
if((num == 1 && cert1HolderReference != null) || (num == 2 && cert2HolderReference != null) || (num != 1 && num != 2)) {
return;
}
short certHolderReferenceOffset = data[OFFSET_SUB_ID_OFFSET];
short certHolderReferenceLength = data[OFFSET_SUB_ID_LENGTH];
short pubKeyExpOffset = data[OFFSET_PUB_KEY_EXPONENT_OFFSET];
short pubKeyExpLength = data[OFFSET_PUB_KEY_EXPONENT_LENGTH];
short pubKeyModOffset = data[OFFSET_PUB_KEY_MODULUS_OFFSET];
short pubKeyModLength = data[OFFSET_PUB_KEY_MODULUS_LENGTH];
short authorizationOffset = data[OFFSET_AUTHORIZATION_OFFSET];
short effDateOffset = data[OFFSET_EFF_DATE_OFFSET];
short expDateOffset = data[OFFSET_EXP_DATE_OFFSET];
byte[] holderReference = new byte[17];
Util.arrayCopyNonAtomic(in, certHolderReferenceOffset, holderReference, (short)1, certHolderReferenceLength);
holderReference[0] = (byte)certHolderReferenceLength;
byte[] certPubKeyData = new byte[(short)(pubKeyExpLength + pubKeyModLength)];
Util.arrayCopyNonAtomic(in, pubKeyExpOffset, certPubKeyData, (short)0, pubKeyExpLength);
Util.arrayCopyNonAtomic(in, pubKeyModOffset, certPubKeyData, pubKeyExpLength, pubKeyModLength);
byte certAuthorization = in[authorizationOffset];
byte[] certEffDate = new byte[6];
Util.arrayCopyNonAtomic(in, effDateOffset, certEffDate, (short)0, (short)6);
byte[] certExpDate = new byte[6];
Util.arrayCopyNonAtomic(in, expDateOffset, certExpDate, (short)0, (short)6);
if(num == 1) {
cert1HolderReference = holderReference;
cert1PublicKeyData = certPubKeyData;
cert1Authorization = certAuthorization;
cert1EffDate = certEffDate;
cert1ExpDate = certExpDate;
}else {
cert2HolderReference = holderReference;
cert2PublicKeyData = certPubKeyData;
cert2Authorization = certAuthorization;
cert2EffDate = certEffDate;
cert2ExpDate = certExpDate;
}
clear();
}
/**
* Parse the current certificate. The data in source/in is analyzed and
* offsets and lengths of particular elements of the certificate are stored
* in the <code>data</code> array. For the root certificate (root == true) we do not
* parse the signature (we have chosen not to provide it). The format of the
* certificate is described in EAC spec version 1.11 App A & C.
*
* @param in
* the array with the certificate to be parsed
* @param offset
* offset to in
* @param length
* length of the data
* @param root
* whether we are parsing a root certificate (no signature)
*/
void parseCertificate(byte[] in, short offset, short length, boolean root) {
try {
offset = BERTLVScanner.readTag(in, offset);
if (BERTLVScanner.tag != TAG_CERT_BODY) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
offset = BERTLVScanner.readLength(in, offset);
offset = BERTLVScanner.readTag(in, offset);
offset = BERTLVScanner.readLength(in, offset);
if (BERTLVScanner.tag != TAG_CERT_VERSION
|| BERTLVScanner.valueLength != (short) 1
|| in[offset] != (byte) 0x00) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
offset = BERTLVScanner.skipValue();
offset = BERTLVScanner.readTag(in, offset);
if (BERTLVScanner.tag != TAG_AUTH_ID) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
BERTLVScanner.readLength(in, offset);
offset = BERTLVScanner.skipValue();
offset = BERTLVScanner.readTag(in, offset);
if (BERTLVScanner.tag != TAG_PUB_KEY) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
offset = BERTLVScanner.readLength(in, offset);
offset = BERTLVScanner.readTag(in, offset);
offset = BERTLVScanner.readLength(in, offset);
if (BERTLVScanner.tag != TAG_OID
|| BERTLVScanner.valueLength != (short) RSA_SHA1_OID.length
|| Util.arrayCompare(in, offset, RSA_SHA1_OID, (short) 0,
(short) RSA_SHA1_OID.length) != 0) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
offset = BERTLVScanner.skipValue();
offset = BERTLVScanner.readTag(in, offset);
if (BERTLVScanner.tag != TAG_MODULUS) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
data[OFFSET_PUB_KEY_MODULUS_OFFSET] = BERTLVScanner.readLength(in,
offset);
data[OFFSET_PUB_KEY_MODULUS_LENGTH] = BERTLVScanner.valueLength;
offset = BERTLVScanner.skipValue();
if (in[data[OFFSET_PUB_KEY_MODULUS_OFFSET]] == (byte) 0x00) {
data[OFFSET_PUB_KEY_MODULUS_OFFSET]++;
data[OFFSET_PUB_KEY_MODULUS_LENGTH]--;
}
offset = BERTLVScanner.readTag(in, offset);
if (BERTLVScanner.tag != TAG_EXPONENT) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
data[OFFSET_PUB_KEY_EXPONENT_OFFSET] = BERTLVScanner.readLength(in,
offset);
data[OFFSET_PUB_KEY_EXPONENT_LENGTH] = BERTLVScanner.valueLength;
offset = BERTLVScanner.skipValue();
if (in[data[OFFSET_PUB_KEY_EXPONENT_OFFSET]] == (byte) 0x00) {
data[OFFSET_PUB_KEY_EXPONENT_OFFSET]++;
data[OFFSET_PUB_KEY_EXPONENT_LENGTH]--;
}
offset = BERTLVScanner.readTag(in, offset);
if (BERTLVScanner.tag != TAG_SUBJECT_ID) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
data[OFFSET_SUB_ID_OFFSET] = BERTLVScanner.readLength(in, offset);
data[OFFSET_SUB_ID_LENGTH] = BERTLVScanner.valueLength;
offset = BERTLVScanner.skipValue();
offset = BERTLVScanner.readTag(in, offset);
offset = BERTLVScanner.readLength(in, offset);
if (BERTLVScanner.tag != TAG_SUBJECT_AUTH
|| BERTLVScanner.valueLength != (short) 14) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
offset = BERTLVScanner.readTag(in, offset);
offset = BERTLVScanner.readLength(in, offset);
if (BERTLVScanner.tag != TAG_OID
|| BERTLVScanner.valueLength != (short)EAC_OID.length
|| Util.arrayCompare(in, offset, EAC_OID,
(short) 0, (short)EAC_OID.length) != 0) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
offset = BERTLVScanner.skipValue();
offset = BERTLVScanner.readTag(in, offset);
data[OFFSET_AUTHORIZATION_OFFSET] = BERTLVScanner.readLength(in,
offset);
if (BERTLVScanner.tag != TAG_AUTHORIZATION
|| BERTLVScanner.valueLength != (short) 1) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
offset = BERTLVScanner.skipValue();
offset = BERTLVScanner.readTag(in, offset);
data[OFFSET_EFF_DATE_OFFSET] = BERTLVScanner.readLength(in, offset);
if (BERTLVScanner.tag != TAG_EFF_DATE
|| BERTLVScanner.valueLength != (short) 6) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
offset = BERTLVScanner.skipValue();
offset = BERTLVScanner.readTag(in, offset);
data[OFFSET_EXP_DATE_OFFSET] = BERTLVScanner.readLength(in, offset);
if (BERTLVScanner.tag != TAG_EXP_DATE
|| BERTLVScanner.valueLength != (short) 6) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
offset = BERTLVScanner.skipValue();
data[OFFSET_BODY_LENGTH] = offset;
if (!root) {
offset = BERTLVScanner.readTag(in, offset);
if (BERTLVScanner.tag != TAG_SIGNATURE) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
data[OFFSET_SIGNATURE_OFFSET] = BERTLVScanner.readLength(in,
offset);
data[OFFSET_SIGNATURE_LENGTH] = BERTLVScanner.valueLength;
source[0] = in;
}
} catch (Exception e){
clear();
ISOException.throwIt((short) (ISO7816.SW_WRONG_DATA));
}
}
/**
* Compares two dates.
*
* @param date1
* the first date
* @param offset1
* offset to the first date
* @param date2
* the second date
* @param offset2
* offset to the second date
* @return -1 if the first date is before the second, 1 if it is after, 0 if
* the same
*/
private byte compareDate(byte[] date1, short offset1, byte[] date2,
short offset2) {
short year1 = (short) ((short) (date1[offset1] * 10) + date1[(short) (offset1 + 1)]);
short year2 = (short) ((short) (date2[offset2] * 10) + date2[(short) (offset2 + 1)]);
short month1 = (short) ((short) (date1[(short) (offset1 + 2)] * 10) + date1[(short) (offset1 + 3)]);
short month2 = (short) ((short) (date2[(short) (offset2 + 2)] * 10) + date2[(short) (offset2 + 3)]);
short day1 = (short) ((short) (date1[(short) (offset1 + 4)] * 10) + date1[(short) (offset1 + 5)]);
short day2 = (short) ((short) (date2[(short) (offset2 + 4)] * 10) + date2[(short) (offset2 + 5)]);
if (year1 < year2) {
return -1;
} else if (year1 > year2) {
return 1;
}
if (month1 < month2) {
return -1;
} else if (month1 > month2) {
return 1;
}
if (day1 < day2) {
return -1;
} else if (day1 > day2) {
return 1;
}
return 0;
}
boolean isDG3Accessible() {
return (byte)(accessFlag[0] & ACCESS_DG3) == ACCESS_DG3;
}
boolean isDG4Accessible() {
return (byte)(accessFlag[0] & ACCESS_DG4) == ACCESS_DG4;
}
}