diff options
3 files changed, 306 insertions, 63 deletions
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/DNIeCard.java b/smcc/src/main/java/at/gv/egiz/smcc/DNIeCard.java index 768ac959..ccb463a5 100644 --- a/smcc/src/main/java/at/gv/egiz/smcc/DNIeCard.java +++ b/smcc/src/main/java/at/gv/egiz/smcc/DNIeCard.java @@ -1,5 +1,25 @@ +/*
+* Copyright 2009 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
package at.gv.egiz.smcc;
+import iaik.me.asn1.ASN1;
+
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
@@ -25,6 +45,9 @@ public class DNIeCard extends AbstractSignatureCard implements SignatureCard { (byte) 0x72, (byte) 0x2E, (byte) 0x46, (byte) 0x69, (byte) 0x6C,
(byte) 0x65 };
+ private final String SIG_KEY_NAME = "KprivFirmaDigital";
+ private final String SIG_CERT_NAME = "CertFirmaDigital";
+
protected PinInfo pinInfo = new PinInfo(8, 16,
"[0-9A-Za-z_<>!()?%\\-=&+\\.]", "at/gv/egiz/smcc/DNIeCard",
"sig.pin", (byte) 0x00, new byte[] {}, PinInfo.UNKNOWN_RETRIES);
@@ -52,9 +75,13 @@ public class DNIeCard extends AbstractSignatureCard implements SignatureCard { }
try {
+
+ byte[] prKdf = executeReadPrKDF(channel);
+ byte[] keyId = getKeyIdFromASN1File(prKdf);
+
verifyPINLoop(channel, pinInfo, pinGUI);
- secureChannel.executeSecureManageSecurityEnvironment(channel);
+ secureChannel.executeSecureManageSecurityEnvironment(channel, keyId);
MessageDigest md;
try {
@@ -88,14 +115,30 @@ public class DNIeCard extends AbstractSignatureCard implements SignatureCard { CardChannel channel = getCardChannel();
- if (!secureChannel.isEstablished())
+ if (!secureChannel.isEstablished()) {
try {
secureChannel.establish(channel);
} catch (CardException e) {
log.debug("Error establishing secure channel to card.", e);
}
+ }
+ byte[] certId = null;
+
+ try {
+ // read CDF file
+ byte[] cdf = executeReadCDF(channel);
+
+ // extract certificate id from ASN1 data structure
+ certId = getCertIdFromASN1File(cdf);
+
+ } catch (CardException e1) {
+
+ log.error("Error reading ASN.1 data!");
+ e1.printStackTrace();
+ }
+
log.debug("Try to read certificate..");
try {
@@ -112,11 +155,11 @@ public class DNIeCard extends AbstractSignatureCard implements SignatureCard { secureChannel.executeSecureSelect(channel, apdu);
- // select 7005
+ // select cert id
byte[] apdu2 = new byte[] {
(byte) 0x00, (byte) 0xA4, (byte) 0x00, (byte) 0x00, (byte) 0x02,
- (byte) 0x70, (byte) 0x05 };
+ certId[certId.length-2], certId[certId.length-1] };
byte[] fci = secureChannel.executeSecureSelect(channel, apdu2);
@@ -126,14 +169,11 @@ public class DNIeCard extends AbstractSignatureCard implements SignatureCard { byte[] data = secureChannel.executeSecureReadBinary(channel,
sizeHi, sizeLo);
- int uncompressedDataLen = getUncompressedDataLength(data);
-
byte[] compressedWithoutHeader = new byte[data.length - 8];
System.arraycopy(data, 8, compressedWithoutHeader, 0,
compressedWithoutHeader.length);
- result = decompressData(compressedWithoutHeader,
- uncompressedDataLen);
+ result = decompressData(compressedWithoutHeader);
} catch (CardException e) {
@@ -218,37 +258,218 @@ public class DNIeCard extends AbstractSignatureCard implements SignatureCard { secureChannel.executeSecureSelect(channel, apdu);
}
- private int getUncompressedDataLength(byte[] data) {
-
- byte len0 = data[0];
- byte len1 = data[1];
- byte len2 = data[2];
- byte len3 = data[3];
+ private byte[] executeReadCDF(CardChannel channel) throws CardException {
+
+ return executeReadFile(channel, new byte[]{(byte)0x50,(byte)0x15,(byte)0x60,(byte)0x04});
+ }
+
+ private byte[] executeReadPrKDF(CardChannel channel) throws CardException {
+
+ return executeReadFile(channel, new byte[]{(byte)0x50,(byte)0x15,(byte)0x60,(byte)0x01});
+ }
+
+ private byte[] getKeyIdFromASN1File(byte[] file) throws CardException {
+
+ // split file in two records
+ int record1Length = getRecordLength(file, 1);
+
+ byte[] record1 = new byte[record1Length];
+ byte[] record2 = new byte[file.length - record1.length];
+
+ System.arraycopy(file, 0, record1, 0, record1.length);
+ System.arraycopy(file, record1.length, record2, 0, record2.length);
+
+ byte[] keyId = new byte[2];
+
+ try {
+ ASN1 asn1_1 = new ASN1(record1);
+ ASN1 asn1_2 = new ASN1(record2);
+
+ if(asn1_1.getElementAt(0).getElementAt(0).gvString().equalsIgnoreCase(SIG_KEY_NAME)) {
+
+ byte[] data = asn1_1.getElementAt(2).gvByteArray();
+
+ keyId[0] = data[9];
+ keyId[1] = data[10];
+ }
- int a = len0;
- int b = len1 * 256;
- int c = len2 * 256 * 256;
- int d = len3 * 256 * 256 * 256;
+ else if(asn1_2.getElementAt(0).getElementAt(0).gvString().equalsIgnoreCase(SIG_KEY_NAME)) {
+
+ byte[] data = asn1_2.getElementAt(2).gvByteArray();
+
+ keyId[0] = data[9];
+ keyId[1] = data[10];
+ }
+
+ } catch (Exception e) {
- return a + b + c + d;
+ throw new CardException("Error getting ASN1 data.", e);
+ }
+
+ return keyId;
}
-
- private byte[] decompressData(byte[] input, int len) throws CardException {
+
+ private byte[] getCertIdFromASN1File(byte[] file) throws CardException {
+
+ int record1Length = getRecordLength(file, 1);
+
+ // split file in two records
+ byte[] record1 = new byte[record1Length];
+ byte[] record2 = new byte[file.length - record1.length];
+
+ System.arraycopy(file, 0, record1, 0, record1.length);
+ System.arraycopy(file, record1.length, record2, 0, record2.length);
+
+ byte[] certId = null;
+
+ try {
+ ASN1 asn1_1 = new ASN1(record1);
+ ASN1 asn1_2 = new ASN1(record2);
+
+ if(asn1_1.getElementAt(0).getElementAt(0).gvString().equalsIgnoreCase(SIG_CERT_NAME)) {
+
+ certId = retrieveCertId(asn1_1.getElementAt(2).gvByteArray());
+ }
+
+ if(asn1_2.getElementAt(0).getElementAt(0).gvString().equalsIgnoreCase(SIG_CERT_NAME)) {
+
+ certId = retrieveCertId(asn1_2.getElementAt(2).gvByteArray());
+ }
+
+ } catch (Exception e) {
+
+ throw new CardException("Error getting ASN1 data.", e);
+ }
+
+ return certId;
+ }
+
+ private byte[] retrieveCertId(byte[] data) throws CardException {
+
+ ASN1 contextSpecific = getASN1WithinContextSpecific(data);
+ try {
+ return contextSpecific.getElementAt(0).getElementAt(0).gvByteArray();
+ } catch (IOException e) {
+ throw new CardException("Error retrieving certificate ID from ASN1 data.", e);
+ }
+ }
+
+ private ASN1 getASN1WithinContextSpecific(byte[] data) throws CardException {
+
+ byte first = data[0];
+ byte lengthOfLength = 0;
+
+ if(first < 0) {
+
+ lengthOfLength = (byte)(first & (byte)0x7F);
+ lengthOfLength = (byte)(lengthOfLength +1);
+ } else {
+
+ lengthOfLength = 1;
+ }
+
+ byte[] asn1data = new byte[data.length - lengthOfLength];
+ System.arraycopy(data, lengthOfLength, asn1data, 0, asn1data.length);
+
+ try {
+ return new ASN1(asn1data);
+ } catch (IOException e) {
+ throw new CardException("Error getting ASN1 structure.", e);
+ }
+ }
+
+ private int getRecordLength(byte[] data, int startOfLength) {
+
+ byte lengthStartByte = data[startOfLength];
+
+ if(lengthStartByte < 0) {
+ // we have more than one length byte
+ byte lengthOfLength = (byte)(lengthStartByte & (byte)0x7F);
+
+ byte[] lengthValues = new byte[lengthOfLength];
+ System.arraycopy(data, startOfLength+1, lengthValues, 0, lengthOfLength);
+
+ int result = 0;
+
+ for(int i=0; i<lengthValues.length; i++) {
+
+ result = (result + byteToInt(lengthValues[lengthValues.length-1-i]) * (int)Math.pow(256, i));
+ }
+
+ return result + startOfLength + lengthOfLength + 1; // defined length + tag byte + length bytes
+
+ } else {
+
+ return (int)lengthStartByte + startOfLength + 1; // defined length + tag byte + length byte
+ }
+
+ }
+
+ private int byteToInt(byte b) {
+
+ return b < 0 ? b + 256 : b;
+
+ }
+
+ private byte[] executeReadFile(CardChannel channel, byte[] path) throws CardException {
+
+ log.debug("Executing secure read File command..");
+
+ executeSecureSelectMasterFile(channel);
+
+ // Select DF
+ byte[] apdu_1 = new byte[] {
+
+ (byte)0x00, // CLA
+ (byte)0xA4, // INS
+ (byte)0x00, // P1
+ (byte)0x00, // P2
+ (byte)0x02, // Lc
+ path[0],
+ path[1]
+ };
+
+ secureChannel.executeSecureSelect(channel, apdu_1);
+
+ // Select EF
+ byte[] apdu_2 = new byte[] {
+
+ (byte)0x00, // CLA
+ (byte)0xA4, // INS
+ (byte)0x00, // P1
+ (byte)0x00, // P2
+ (byte)0x02, // Lc
+ path[2],
+ path[3]
+ };
+
+ byte[] fci = secureChannel.executeSecureSelect(channel, apdu_2);
+ byte[] file = secureChannel.executeSecureReadBinary(channel, fci[7], fci[8]);
+
+ return file;
+ }
+
+ private byte[] decompressData(byte[] input) throws CardException {
Inflater decompresser = new Inflater();
decompresser.setInput(input, 0, input.length);
- byte[] result = new byte[len];
-
+ byte[] buffer = new byte[256];
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
try {
- decompresser.inflate(result);
- decompresser.end();
-
- return result;
+ while(!decompresser.finished()) {
+
+ int numBytes = decompresser.inflate(buffer);
+ bos.write(buffer, 0, numBytes);
+ }
+ decompresser.end();
+
} catch (DataFormatException e) {
throw new CardException("Error decompressing file.", e);
}
- }
+ return bos.toByteArray();
+ }
}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/DNIeCardSecureChannel.java b/smcc/src/main/java/at/gv/egiz/smcc/DNIeCardSecureChannel.java index 5f789922..da63bec2 100644 --- a/smcc/src/main/java/at/gv/egiz/smcc/DNIeCardSecureChannel.java +++ b/smcc/src/main/java/at/gv/egiz/smcc/DNIeCardSecureChannel.java @@ -1,3 +1,20 @@ +/*
+* Copyright 2009 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
package at.gv.egiz.smcc;
import java.io.ByteArrayInputStream;
@@ -188,8 +205,6 @@ public class DNIeCardSecureChannel { private final byte[] IV = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00 };
- private final byte[] KEY_ID = new byte[] { (byte) 0x01, (byte) 0x02 };
-
private final byte[] HASH_PADDING = new byte[] {
(byte) 0x30, (byte) 0x21, (byte) 0x30, (byte) 0x09, (byte) 0x06,
@@ -220,10 +235,12 @@ public class DNIeCardSecureChannel { public void establish(CardChannel channel) throws CardException {
+ log.debug("Setting up secure channel to crd..");
+
// get chip info
this.snIcc = executeGetChipInfo(channel);
- // get certificates to establish secure channel
+ // get card certificates to establish secure channel
this.intermediateCert = executeReadSecureChannelCertificate(channel,
SECURE_CHANNEL_INTERMEDIAT_CERT_ID);
this.componentCert = executeReadSecureChannelCertificate(channel,
@@ -288,20 +305,21 @@ public class DNIeCardSecureChannel { }
- public void executeSecureManageSecurityEnvironment(CardChannel channel)
+ public void executeSecureManageSecurityEnvironment(CardChannel channel, byte[] id)
throws CardException {
log.debug("Manage Security Environment..");
- byte[] apdu = new byte[7 + KEY_ID.length];
+ byte[] apdu = new byte[7 + 2];
apdu[0] = (byte) 0x00;
apdu[1] = (byte) 0x22;
apdu[2] = (byte) 0x41;
apdu[3] = (byte) 0xB6;
- apdu[4] = (byte) (KEY_ID.length + 2);
+ apdu[4] = (byte) (2 + 2);
apdu[5] = (byte) 0x84; // Tag
- apdu[6] = (byte) KEY_ID.length; // Length
- System.arraycopy(KEY_ID, 0, apdu, 7, KEY_ID.length);
+ apdu[6] = (byte) 0x02; // Length
+ apdu[7] = id[0]; // ID
+ apdu[8] = id[1]; // ID
byte[] securedAPDU = getSecuredAPDU(apdu);
@@ -479,9 +497,6 @@ public class DNIeCardSecureChannel { System.arraycopy(encrypted, 0, encapsulated, 3, encrypted.length);
// calculate MAC
-
- // prepare CLA byte
-
byte encCLA = (byte) (cla | (byte) 0x0C);
byte[] encHeader = new byte[] { encCLA, ins, p1, p2 };
byte[] paddedHeader = applyPadding(BLOCK_LENGTH, encHeader);
@@ -733,8 +748,6 @@ public class DNIeCardSecureChannel { byte[] decryptedResponse = verifyAndDecryptSecuredResponseAPDU(data);
- log.debug("Read plain data: " + formatByteArray(decryptedResponse));
-
return decryptedResponse;
}
@@ -869,7 +882,7 @@ public class DNIeCardSecureChannel { byte[] prnd2 = getRandomBytes(this.prndLength);
byte[] kIfd = getRandomBytes(32);
-
+
// compute hash
byte[] hashData = new byte[prnd2.length + kIfd.length
+ cardChallenge.length + BLOCK_LENGTH];
@@ -888,9 +901,9 @@ public class DNIeCardSecureChannel { System.arraycopy(snIcc, 0, hashData, prnd2.length + kIfd.length
+ cardChallenge.length + snPadding, snIcc.length);
-
+
byte[] digest = computeSHA1Hash(hashData);
-
+
// prepare data to be encrypted
byte[] plain = new byte[2 + prnd2.length + kIfd.length + digest.length];
@@ -906,17 +919,17 @@ public class DNIeCardSecureChannel { // encrypt plain data
RSAPrivateKey terminalPrivateKey = createRSAPrivateKey(TERMINAL_MODULO,
TERMINAL_PRIVEXP);
-
+
byte[] encResult = null;
try {
encResult = rsaEncrypt(terminalPrivateKey, plain);
} catch (Exception e) {
-
+ e.printStackTrace();
throw new CardException("Error encrypting authentication data.", e);
}
- // apply MIN function
- BigInteger sig = new BigInteger(encResult);
+ // apply MIN function
+ BigInteger sig = createUnsignedBigInteger(encResult);
BigInteger mod = new BigInteger(TERMINAL_MODULO, 16);
BigInteger diff = mod.subtract(sig);
@@ -932,10 +945,11 @@ public class DNIeCardSecureChannel { try {
authData = rsaEncrypt(cardPubKey, sigMin.toByteArray());
} catch (Exception e) {
-
+ e.printStackTrace();
throw new CardException("Error encrypting authentication data.");
}
+ log.debug("Sending computed cryptogram to card..");
// send auth data to card
// BE CAREFUL WITH THAT! EXT-AUTH METHOD MAY GET BLOCKED!
if (executeExternalAuthenticate(channel, authData)) {
@@ -962,6 +976,8 @@ public class DNIeCardSecureChannel { private void performInternalAuthentication(CardChannel channel)
throws CardException {
+ log.debug("Starting internal authentication..");
+
byte[] randomBytes = getRandomBytes(BLOCK_LENGTH);
byte[] challengeData = new byte[randomBytes.length
+ TERMINAL_CHALLENGE_TAIL.length];
@@ -991,7 +1007,6 @@ public class DNIeCardSecureChannel { // This method verifies the card's component and intermediate
// certificates cryptographically.
- // TODO: Revocation checking (cannot be done now since CRL URLs in certificates seem to be incorrect)
RSAPublicKey rootPubKey = createRSAPublicKey(ROOT_CA_MODULO,
ROOT_CA_PUBEXP);
@@ -1004,8 +1019,7 @@ public class DNIeCardSecureChannel { intermediate.verify(rootPubKey);
} catch (Exception e) {
- log.error("Certificate verification failed.");
- e.printStackTrace();
+ throw new CardException("Certificate verification failed.", e);
}
}
@@ -1044,27 +1058,27 @@ public class DNIeCardSecureChannel { if (sig == null) {
- throw new CardException("Invalid decryption result - null.");
+ throw new CardException("Invalid decryption result: null.");
} else {
if (sig[0] == (byte) 0x6A && sig[sig.length - 1] == (byte) 0xBC) {
// Obtained response from card was obviously SIG - nothing else
- // to do here
+ // to do here so far
} else {
- // Obtained response from card was obviously N.ICC-SIG -
+ // Obtained response from card was probably N.ICC-SIG -
// compute N.ICC-SIG and decrypt result again
RSAPublicKey rsaPubKey = (RSAPublicKey) pubKey;
BigInteger mod = rsaPubKey.getModulus();
- BigInteger sigVal = new BigInteger(plain);
+ BigInteger sigVal = createUnsignedBigInteger(plain);
BigInteger substractionResult = mod.subtract(sigVal);
byte[] encrypted = substractionResult.toByteArray();
- // necessary as substraction result seems to contain one leading
+ // necessary if substraction result contains leading
// zero byte
byte[] trimmed = new byte[128];
System.arraycopy(encrypted, encrypted.length - 128, trimmed, 0,
@@ -1148,8 +1162,6 @@ public class DNIeCardSecureChannel { log.debug("FAILED: " + Integer.toHexString(resp.getSW()));
}
- log.debug("Read chip info: " + formatByteArray(resp.getData()));
-
return resp.getData();
}
@@ -1430,6 +1442,16 @@ public class DNIeCardSecureChannel { return result;
}
+ private BigInteger createUnsignedBigInteger(byte[] data) {
+
+ byte[] unsigned = new byte[data.length+1];
+ unsigned[0] = (byte)0x00;
+ System.arraycopy(data, 0, unsigned, 1, data.length);
+
+ return new BigInteger(unsigned);
+
+ }
+
private RSAPrivateKey createRSAPrivateKey(String mod, String privExponent)
throws CardException {
@@ -1473,7 +1495,7 @@ public class DNIeCardSecureChannel { private byte[] rsaEncrypt(Key key, byte[] data)
throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
-
+
Cipher rsa = Cipher.getInstance("RSA/ECB/NoPadding");
rsa.init(Cipher.ENCRYPT_MODE, key);
byte[] encrypted = rsa.doFinal(data);
@@ -1622,5 +1644,5 @@ public class DNIeCardSecureChannel { return (sw1 * 256) + sw2;
}
-
+
}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/SignatureCardFactory.java b/smcc/src/main/java/at/gv/egiz/smcc/SignatureCardFactory.java index a6056de5..113f00cb 100644 --- a/smcc/src/main/java/at/gv/egiz/smcc/SignatureCardFactory.java +++ b/smcc/src/main/java/at/gv/egiz/smcc/SignatureCardFactory.java @@ -276,12 +276,12 @@ public class SignatureCardFactory { new byte[] { (byte) 0x3b, (byte) 0x7F, (byte) 0x38, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x6A, (byte) 0x44, (byte) 0x4E, (byte) 0x49, (byte) 0x65, - (byte) 0x20, (byte) 0x02, (byte) 0x4C, (byte) 0x34, (byte) 0x01, (byte) 0x13, (byte) 0x03, (byte) 0x90, (byte) 0x00 }, + (byte) 0x00, (byte) 0x02, (byte) 0x4C, (byte) 0x34, (byte) 0x01, (byte) 0x13, (byte) 0x03, (byte) 0x90, (byte) 0x00 }, // mask (ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff) new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, - (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff }, + (byte) 0x00, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff }, "at.gv.egiz.smcc.DNIeCard")); // ITCards |