/*
* eID Applet Project.
* Copyright (C) 2008-2009 FedICT.
* Copyright (C) 2014-2015 e-Contract.be BVBA.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version
* 3.0 as published by the Free Software Foundation.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, see
* http://www.gnu.org/licenses/.
*/
package test.be.fedict.eid.applet;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.Security;
import java.security.Signature;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import javax.crypto.Cipher;
import javax.imageio.ImageIO;
import javax.smartcardio.Card;
import javax.smartcardio.CardChannel;
import javax.smartcardio.CardException;
import javax.smartcardio.CardTerminal;
import javax.smartcardio.CardTerminals;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.ResponseAPDU;
import javax.smartcardio.TerminalFactory;
import javax.swing.ImageIcon;
import javax.swing.JOptionPane;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.util.ASN1Dump;
import org.bouncycastle.asn1.x509.CertificateList;
import org.bouncycastle.asn1.x509.DigestInfo;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.jce.PrincipalUtil;
import org.bouncycastle.jce.X509Principal;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMWriter;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import be.fedict.eid.applet.Messages;
import be.fedict.eid.applet.View;
import be.fedict.eid.applet.sc.Constants;
import be.fedict.eid.applet.sc.PcscEid;
import be.fedict.eid.applet.sc.Task;
import be.fedict.eid.applet.sc.TaskRunner;
import be.fedict.eid.applet.service.Address;
import be.fedict.eid.applet.service.Identity;
import be.fedict.eid.applet.service.impl.tlv.TlvField;
import be.fedict.eid.applet.service.impl.tlv.TlvParser;
import be.fedict.trust.BelgianTrustValidatorFactory;
import be.fedict.trust.FallbackTrustLinker;
import be.fedict.trust.MemoryCertificateRepository;
import be.fedict.trust.NetworkConfig;
import be.fedict.trust.PublicKeyTrustLinker;
import be.fedict.trust.RevocationData;
import be.fedict.trust.TrustValidator;
import be.fedict.trust.crl.CachedCrlRepository;
import be.fedict.trust.crl.CrlTrustLinker;
import be.fedict.trust.crl.OnlineCrlRepository;
import be.fedict.trust.ocsp.OcspTrustLinker;
import be.fedict.trust.ocsp.OnlineOcspRepository;
/**
* Integration tests for PC/SC eID component.
*
* @author Frank Cornelis
*
*/
public class PcscTest {
static final Log LOG = LogFactory.getLog(PcscTest.class);
@BeforeClass
public static void beforeClass() {
Security.addProvider(new BouncyCastleProvider());
}
private Messages messages;
@Before
public void setUp() {
this.messages = new Messages(Locale.getDefault());
}
@Test
public void pcscAuthnSignature() throws Exception {
this.messages = new Messages(Locale.GERMAN);
PcscEid pcscEid = new PcscEid(new TestView(), this.messages);
if (false == pcscEid.isEidPresent()) {
LOG.debug("insert eID card");
pcscEid.waitForEidPresent();
}
// PPDU test
pcscEid.addPPDUName("digipass");
byte[] challenge = "hello world".getBytes();
byte[] signatureValue;
List<X509Certificate> authnCertChain;
try {
// pcscEid.logoff();
// pcscEid.selectBelpicJavaCardApplet();
signatureValue = pcscEid.signAuthn(challenge);
long t0 = System.currentTimeMillis();
pcscEid.signAuthn(challenge);
long t1 = System.currentTimeMillis();
LOG.debug("dt: " + (t1 - t0));
authnCertChain = pcscEid.getAuthnCertificateChain();
LOG.debug("key size: " + authnCertChain.get(0).getPublicKey().getEncoded().length * 8);
// pcscEid.logoff();
} finally {
pcscEid.close();
}
Signature signature = Signature.getInstance("SHA1withRSA");
signature.initVerify(authnCertChain.get(0).getPublicKey());
signature.update(challenge);
boolean result = signature.verify(signatureValue);
assertTrue(result);
LOG.debug("sha1 hex: " + DigestUtils.shaHex(authnCertChain.get(0).getPublicKey().getEncoded()));
}
@Test
public void pcscMSE_SET() throws Exception {
this.messages = new Messages(Locale.GERMAN);
PcscEid pcscEid = new PcscEid(new TestView(), this.messages);
if (false == pcscEid.isEidPresent()) {
LOG.debug("insert eID card");
pcscEid.waitForEidPresent();
}
CardChannel cardChannel = pcscEid.getCardChannel();
try {
CommandAPDU setApdu = new CommandAPDU(0x00, 0x22, 0x41, 0xB6,
new byte[] { 0x04, // length of following data
(byte) 0x80, // algo ref
// 0x01, // rsa pkcs#1
// 0x02, // PKCS1-SHA1
// 0x04, // PKCS1-MD5
// 0x08, // PKCS1-SHA256
// 0x10, // PKCS1-PSS-SHA1
0x20, // PKCS1-PSS-SHA256
// (byte) 0xfb, // foobar
(byte) 0x84, // tag for private key ref
PcscEid.AUTHN_KEY_ID });
ResponseAPDU responseAPDU = cardChannel.transmit(setApdu);
assertEquals(0x9000, responseAPDU.getSW());
} finally {
pcscEid.close();
}
}
@Test
public void createPSSSignature() throws Exception {
this.messages = new Messages(Locale.GERMAN);
PcscEid pcscEid = new PcscEid(new TestView(), this.messages);
if (false == pcscEid.isEidPresent()) {
LOG.debug("insert eID card");
pcscEid.waitForEidPresent();
}
CardChannel cardChannel = pcscEid.getCardChannel();
byte[] message = "hello world".getBytes();
MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
byte[] digest = messageDigest.digest(message);
try {
CommandAPDU setApdu = new CommandAPDU(0x00, 0x22, 0x41, 0xB6,
new byte[] { 0x04, // length of following data
(byte) 0x80, // algo ref
0x10, // PKCS1-PSS-SHA1
(byte) 0x84, // tag for private key ref
PcscEid.AUTHN_KEY_ID });
ResponseAPDU responseAPDU = cardChannel.transmit(setApdu);
assertEquals(0x9000, responseAPDU.getSW());
pcscEid.verifyPin();
CommandAPDU computeDigitalSignatureApdu = new CommandAPDU(0x00, 0x2A, 0x9E, 0x9A, digest);
responseAPDU = cardChannel.transmit(computeDigitalSignatureApdu);
assertEquals(0x9000, responseAPDU.getSW());
byte[] signatureValue = responseAPDU.getData();
LOG.debug("signature value length: " + signatureValue.length);
List<X509Certificate> authnCertificateChain = pcscEid.getAuthnCertificateChain();
Signature signature = Signature.getInstance("SHA1withRSA/PSS", "BC");
signature.initVerify(authnCertificateChain.get(0).getPublicKey());
signature.update(message);
boolean result = signature.verify(signatureValue);
assertTrue(result);
} finally {
pcscEid.close();
}
}
@Test
public void pcscOTPSpike() throws Exception {
this.messages = new Messages(Locale.GERMAN);
PcscEid pcscEid = new PcscEid(new TestView(), this.messages);
if (false == pcscEid.isEidPresent()) {
LOG.debug("insert eID card");
pcscEid.waitForEidPresent();
}
byte[] challenge1 = "123456".getBytes();
byte[] challenge2 = "654321".getBytes();
byte[] signatureValue1;
byte[] signatureValue2;
List<X509Certificate> authnCertChain;
try {
signatureValue1 = pcscEid.signAuthn(challenge1);
signatureValue2 = pcscEid.signAuthn(challenge2);
authnCertChain = pcscEid.getAuthnCertificateChain();
} finally {
pcscEid.close();
}
byte[] sv1 = Arrays.copyOf(signatureValue1, 13);
byte[] sv2 = Arrays.copyOf(signatureValue2, 13);
LOG.debug("same encrypted prefix: " + Arrays.equals(sv1, sv2));
Signature signature = Signature.getInstance("SHA1withRSA");
signature.initVerify(authnCertChain.get(0).getPublicKey());
signature.update(challenge1);
boolean result = signature.verify(signatureValue1);
assertTrue(result);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, authnCertChain.get(0).getPublicKey());
byte[] signatureDigestInfoValue = cipher.doFinal(signatureValue1);
LOG.debug("encrypted signature value: " + signatureValue1.length);
ASN1InputStream aIn = new ASN1InputStream(signatureDigestInfoValue);
DigestInfo signatureDigestInfo = new DigestInfo((ASN1Sequence) aIn.readObject());
LOG.debug("algo OID: " + signatureDigestInfo.getAlgorithmId().getObjectId().getId());
LOG.debug("digest size: " + signatureDigestInfo.getDigest().length);
int digestIndex = findSubArray(signatureDigestInfoValue, signatureDigestInfo.getDigest());
assertTrue(-1 != digestIndex);
LOG.debug("digest index: " + digestIndex);
// inject the encrypted digest of signature1 into signature2
// padding will look bad now
System.arraycopy(signatureValue1, 13, signatureValue2, 13, 20);
cipher = Cipher.getInstance("RSA/ECB/nopadding");
cipher.init(Cipher.DECRYPT_MODE, authnCertChain.get(0).getPublicKey());
signatureValue2 = Arrays.copyOf(signatureValue2, 13 + 20);
byte[] signatureDigestInfoValue2 = cipher.doFinal(signatureValue2);
LOG.debug("decrypted structure size: " + signatureDigestInfoValue2.length);
signatureDigestInfoValue2 = Arrays.copyOf(signatureDigestInfoValue2, 13 + 20);
LOG.debug("decrypted structure size (truncated): " + signatureDigestInfoValue2.length);
ASN1InputStream aIn2 = new ASN1InputStream(signatureDigestInfoValue2);
DigestInfo signatureDigestInfo2 = new DigestInfo((ASN1Sequence) aIn2.readObject());
LOG.debug("digest size: " + signatureDigestInfo2.getDigest().length);
LOG.debug("digest: " + new String(signatureDigestInfo2.getDigest()));
}
private int findSubArray(byte[] array, byte[] subarray) {
LOG.debug("array size: " + array.length);
LOG.debug("subarray size: " + subarray.length);
for (int idx = 0; idx < array.length - subarray.length + 1; idx++) {
byte[] currentSubArray = Arrays.copyOfRange(array, idx, idx + subarray.length);
LOG.debug("subarray size: " + currentSubArray.length);
if (Arrays.equals(currentSubArray, subarray)) {
return idx;
}
}
return -1;
}
@Test
public void pcscAuthnSignatureWithCardRemoval() throws Exception {
PcscEid pcscEid = new PcscEid(new TestView(), this.messages);
if (false == pcscEid.isEidPresent()) {
LOG.debug("insert eID card");
pcscEid.waitForEidPresent();
}
byte[] challenge = "hello world".getBytes();
try {
pcscEid.signAuthn(challenge);
pcscEid.getAuthnCertificateChain();
LOG.debug("remove card");
pcscEid.removeCard();
} finally {
pcscEid.close();
}
}
@Test
public void testLocale() throws Exception {
Locale locale = Locale.GERMAN;
LOG.debug("locale: " + locale.getLanguage());
}
@Test
public void testCardSignature() throws Exception {
PcscEid pcscEid = new PcscEid(new TestView(), this.messages);
if (false == pcscEid.isEidPresent()) {
LOG.debug("insert eID card");
pcscEid.waitForEidPresent();
}
try {
CardChannel cardChannel = pcscEid.getCardChannel();
CommandAPDU setApdu = new CommandAPDU(0x00, 0x22, 0x41, 0xB6,
new byte[] { 0x04, // length of following data
(byte) 0x80, // algo ref
0x01, // rsa pkcs#1
(byte) 0x84, // tag for private key ref
(byte) 0x81 });
ResponseAPDU responseApdu = cardChannel.transmit(setApdu);
if (0x9000 != responseApdu.getSW()) {
throw new RuntimeException("SELECT error");
}
byte[] message = "hello world".getBytes();
MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
byte[] digestValue = messageDigest.digest(message);
ByteArrayOutputStream digestInfo = new ByteArrayOutputStream();
digestInfo.write(Constants.SHA1_DIGEST_INFO_PREFIX);
digestInfo.write(digestValue);
CommandAPDU computeDigitalSignatureApdu = new CommandAPDU(0x00, 0x2A, 0x9E, 0x9A, digestInfo.toByteArray());
responseApdu = cardChannel.transmit(computeDigitalSignatureApdu);
if (0x9000 != responseApdu.getSW()) {
throw new RuntimeException("error CDS: " + Integer.toHexString(responseApdu.getSW()));
}
} finally {
pcscEid.close();
}
}
@Test
public void logoff() throws Exception {
PcscEid pcscEidSpi = new PcscEid(new TestView(), this.messages);
if (false == pcscEidSpi.isEidPresent()) {
LOG.debug("insert eID card");
pcscEidSpi.waitForEidPresent();
}
pcscEidSpi.logoff();
pcscEidSpi.close();
}
@Test
public void signWhatever() throws Exception {
PcscEid pcscEid = new PcscEid(new TestView(), this.messages);
if (false == pcscEid.isEidPresent()) {
LOG.debug("insert eID card");
pcscEid.waitForEidPresent();
}
CardChannel cardChannel = pcscEid.getCardChannel();
CommandAPDU setApdu = new CommandAPDU(0x00, 0x22, 0x41, 0xB6,
new byte[] { 0x04, // length of following data
(byte) 0x80, // algo ref
0x01, // rsa pkcs#1
(byte) 0x84, // tag for private key ref
(byte) 0x82 }); // auth key
ResponseAPDU responseApdu = cardChannel.transmit(setApdu);
assertEquals(0x9000, responseApdu.getSW());
pcscEid.verifyPin();
// CommandAPDU computeDigitalSignatureApdu = new CommandAPDU(0x00, 0x2A,
// 0x9E, 0x9A, new byte[] {
// 0x30, // DER
// 0x1f, // length
// 0x30, // DER
// 0x07, // length
// // OID = SHA1
// 0x06, // OID tag
// 0x05, 0x2b, 0x0e, 0x03,
// 0x02,
// 0x1a,
// 0x04, // tag OCTET STRING
// 0x14, // length
// 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
// 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12,
// 0x13, 0x14 });
// CommandAPDU computeDigitalSignatureApdu = new CommandAPDU(0x00, 0x2A,
// 0x9E, 0x9A, new byte[] {
// 0x30, // DER DigestInfo
// 0x18, // length
// 0x30, // DER AlgorithmIdentifier
// 0x00, // length: no OID
// 0x04, // tag OCTET STRING
// 0x14, // length
// 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
// 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12,
// 0x13, 0x14 });
CommandAPDU computeDigitalSignatureApdu = new CommandAPDU(0x00, 0x2A, 0x9E, 0x9A,
"Hello world encrypted".getBytes());
responseApdu = cardChannel.transmit(computeDigitalSignatureApdu);
assertEquals(0x9000, responseApdu.getSW());
byte[] signatureValue = responseApdu.getData();
LOG.debug("signature value size: " + signatureValue.length);
List<X509Certificate> authnCertChain = pcscEid.getAuthnCertificateChain();
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, authnCertChain.get(0).getPublicKey());
byte[] decryptedSignatureValue = cipher.doFinal(signatureValue);
LOG.debug("decrypted signature value: " + new String(decryptedSignatureValue));
pcscEid.close();
}
@Test
public void logoffAndDie() throws Exception {
PcscEid pcscEidSpi = new PcscEid(new TestView(), this.messages);
if (false == pcscEidSpi.isEidPresent()) {
LOG.debug("insert eID card");
pcscEidSpi.waitForEidPresent();
}
try {
do {
pcscEidSpi.logoff();
} while (true);
} finally {
pcscEidSpi.close();
}
}
@Test
public void pcscChangePin() throws Exception {
this.messages = new Messages(Locale.GERMAN);
PcscEid pcscEidSpi = new PcscEid(new TestView(), this.messages);
if (false == pcscEidSpi.isEidPresent()) {
LOG.debug("insert eID card");
pcscEidSpi.waitForEidPresent();
}
// PPDU test
pcscEidSpi.addPPDUName("digipass 870");
pcscEidSpi.changePin();
pcscEidSpi.close();
}
@Test
public void pcscUnblockPin() throws Exception {
PcscEid pcscEidSpi = new PcscEid(new TestView(), this.messages);
if (false == pcscEidSpi.isEidPresent()) {
LOG.debug("insert eID card");
pcscEidSpi.waitForEidPresent();
}
// PPDU test
pcscEidSpi.addPPDUName("digipass 870");
pcscEidSpi.unblockPin();
pcscEidSpi.close();
}
@Test
public void photo() throws Exception {
PcscEid pcscEidSpi = new PcscEid(new TestView(), this.messages);
if (false == pcscEidSpi.isEidPresent()) {
LOG.debug("insert eID card");
pcscEidSpi.waitForEidPresent();
}
long t0 = System.currentTimeMillis();
// pcscEidSpi.selectBelpicJavaCardApplet();
byte[] photo = pcscEidSpi.readFile(PcscEid.PHOTO_FILE_ID);
long t1 = System.currentTimeMillis();
LOG.debug("image size: " + photo.length);
BufferedImage image = ImageIO.read(new ByteArrayInputStream(photo));
assertNotNull(image);
LOG.debug("width: " + image.getWidth());
LOG.debug("height: " + image.getHeight());
LOG.debug("dt: " + (t1 - t0) + " ms");
pcscEidSpi.close();
}
@Test
public void testReadPhoto() throws Exception {
TerminalFactory terminalFactory = TerminalFactory.getDefault();
CardTerminals cardTerminals = terminalFactory.terminals();
CardTerminal cardTerminal = cardTerminals.list().get(0);
Card card = cardTerminal.connect("T=0");
CardChannel cardChannel = card.getBasicChannel();
// select file
cardChannel.transmit(
new CommandAPDU(0x00, 0xA4, 0x08, 0x0C, new byte[] { 0x3F, 0x00, (byte) 0xDF, 0x01, 0x40, 0x35 }));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int offset = 0;
ResponseAPDU responseApdu;
do {
// read binary
responseApdu = cardChannel.transmit(new CommandAPDU(0x00, 0xB0, offset >> 8, offset & 0xFF, 0xff));
baos.write(responseApdu.getData());
offset += responseApdu.getData().length;
} while (responseApdu.getData().length == 0xff);
BufferedImage photo = ImageIO.read(new ByteArrayInputStream(baos.toByteArray()));
JOptionPane.showMessageDialog(null, new ImageIcon(photo));
}
@Test
public void testReadAddress() throws Exception {
PcscEid pcscEidSpi = new PcscEid(new TestView(), this.messages);
if (false == pcscEidSpi.isEidPresent()) {
LOG.debug("insert eID card");
pcscEidSpi.waitForEidPresent();
}
pcscEidSpi.readFile(PcscEid.IDENTITY_FILE_ID);
byte[] addressFile = pcscEidSpi.readFile(PcscEid.ADDRESS_FILE_ID);
pcscEidSpi.selectBelpicJavaCardApplet();
pcscEidSpi.close();
Address address = TlvParser.parse(addressFile, Address.class);
LOG.debug("street and number: " + address.getStreetAndNumber());
LOG.debug("zip: " + address.getZip());
LOG.debug("municipality: " + address.getMunicipality());
}
@Test
public void testReadNonRepudiationCertificate() throws Exception {
PcscEid pcscEidSpi = new PcscEid(new TestView(), this.messages);
if (false == pcscEidSpi.isEidPresent()) {
LOG.debug("insert eID card");
pcscEidSpi.waitForEidPresent();
}
File tmpFile = File.createTempFile("eid-sign-cert-", ".der");
byte[] signCert = pcscEidSpi.readFile(PcscEid.SIGN_CERT_FILE_ID);
FileUtils.writeByteArrayToFile(tmpFile, signCert);
LOG.debug("ASN1 dump: " + ASN1Dump.dumpAsString(new ASN1InputStream(signCert).readObject()));
LOG.debug("tmp file: " + tmpFile.getAbsolutePath());
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate certificate = (X509Certificate) certificateFactory
.generateCertificate(new ByteArrayInputStream(signCert));
X509Principal issuerPrincipal = PrincipalUtil.getIssuerX509Principal(certificate);
LOG.debug("BC issuer principal: " + issuerPrincipal.getName());
LOG.debug("Sun issuer (getName): " + certificate.getIssuerX500Principal().getName());
LOG.debug("Sun issuer (toString): " + certificate.getIssuerX500Principal());
String issuerName = PrincipalUtil.getIssuerX509Principal(certificate).getName().replace(",", ", ");
LOG.debug("issuer name: " + issuerName);
LOG.debug("certificate: " + certificate);
pcscEidSpi.close();
}
@Test
public void testDEREncoding() throws Exception {
PcscEid pcscEidSpi = new PcscEid(new TestView(), this.messages);
if (false == pcscEidSpi.isEidPresent()) {
LOG.debug("insert eID card");
pcscEidSpi.waitForEidPresent();
}
try {
byte[] authnCert = pcscEidSpi.readFile(PcscEid.AUTHN_CERT_FILE_ID);
DERSequence sequence = (DERSequence) new ASN1InputStream(new ByteArrayInputStream(authnCert)).readObject();
String str = ASN1Dump.dumpAsString(sequence);
LOG.debug(str);
} finally {
pcscEidSpi.close();
}
}
private void selectCardManager(CardChannel cardChannel) {
CommandAPDU selectApplicationApdu = new CommandAPDU(0x00, 0xA4, 0x04, 0x00);
ResponseAPDU responseApdu;
try {
responseApdu = cardChannel.transmit(selectApplicationApdu);
} catch (CardException e) {
LOG.debug("error selecting application");
return;
} catch (ArrayIndexOutOfBoundsException e) {
LOG.debug("array error");
return;
}
if (0x9000 != responseApdu.getSW()) {
LOG.debug("could not select application");
} else {
LOG.debug("application selected");
}
}
@Test
public void testSelectBelpic() throws Exception {
final PcscEid pcscEid = new PcscEid(new TestView(), this.messages);
if (false == pcscEid.isEidPresent()) {
LOG.debug("insert eID card");
pcscEid.waitForEidPresent();
}
try {
pcscEid.selectBelpicJavaCardApplet();
} finally {
pcscEid.close();
}
}
/**
* @throws Exception
*/
@Test
public void testRetrievePIN() throws Exception {
final PcscEid pcscEid = new PcscEid(new TestView(), this.messages);
if (false == pcscEid.isEidPresent()) {
LOG.debug("insert eID card");
pcscEid.waitForEidPresent();
}
byte[] puk12 = new byte[] { 0x22, 0x22, 0x22, 0x11, 0x11, 0x11 };
try {
CardChannel cardChannel = pcscEid.getCardChannel();
for (int pin = 9999; pin >= 0; pin--) {
LOG.debug("trying PIN: " + pin);
byte[] bcdPin = new byte[2];
int dec = pin;
bcdPin[1] = (byte) (dec % 10);
dec /= 10;
bcdPin[1] |= (byte) (dec % 10) << 4;
dec /= 10;
bcdPin[0] = (byte) (dec % 10);
dec /= 10;
bcdPin[0] |= (byte) (dec % 10) << 4;
ResponseAPDU responseApdu = verifyPin(bcdPin, cardChannel);
int sw = responseApdu.getSW();
if (0x9000 == sw) {
LOG.debug("PIN is: " + pin);
break;
}
if (0x6983 == sw) {
unblockPin(puk12, cardChannel);
}
}
} finally {
pcscEid.close();
}
}
private void unblockPin(byte[] puk12, CardChannel cardChannel) throws CardException {
byte[] unblockPinData = new byte[] { 0x2C, puk12[0], puk12[1], puk12[2], puk12[3], puk12[4], puk12[5],
(byte) 0xFF };
CommandAPDU changePinApdu = new CommandAPDU(0x00, 0x2C, 0x00, 0x01, unblockPinData);
ResponseAPDU responseApdu = cardChannel.transmit(changePinApdu);
if (0x9000 != responseApdu.getSW()) {
throw new RuntimeException("could not unblock PIN code");
}
}
private ResponseAPDU verifyPin(byte[] pin, CardChannel cardChannel) throws CardException {
byte[] verifyData = new byte[] { 0x24, pin[0], pin[1], (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
(byte) 0xFF };
CommandAPDU verifyApdu = new CommandAPDU(0x00, 0x20, 0x00, 0x01, verifyData);
ResponseAPDU responseApdu = cardChannel.transmit(verifyApdu);
return responseApdu;
}
@Test
public void testCardManager() throws Exception {
final PcscEid pcscEid = new PcscEid(new TestView(), this.messages);
if (false == pcscEid.isEidPresent()) {
LOG.debug("insert eID card");
pcscEid.waitForEidPresent();
}
View view = new TestView();
CardChannel cardChannel = pcscEid.getCardChannel();
selectCardManager(cardChannel);
// card manager active
TaskRunner taskRunner = new TaskRunner(pcscEid, view);
try {
byte[] data = taskRunner.run(new Task<byte[]>() {
public byte[] run() throws Exception {
return pcscEid.readFile(PcscEid.IDENTITY_FILE_ID);
}
});
assertNotNull(data);
} finally {
pcscEid.close();
}
}
@Test
public void displayCitizenCertificates() throws Exception {
PcscEid pcscEidSpi = new PcscEid(new TestView(), this.messages);
if (false == pcscEidSpi.isEidPresent()) {
LOG.debug("insert eID card");
pcscEidSpi.waitForEidPresent();
}
byte[] authnCertData = pcscEidSpi.readFile(PcscEid.AUTHN_CERT_FILE_ID);
byte[] signCertData = pcscEidSpi.readFile(PcscEid.SIGN_CERT_FILE_ID);
byte[] citizenCaCertData = pcscEidSpi.readFile(PcscEid.CA_CERT_FILE_ID);
byte[] rootCaCertData = pcscEidSpi.readFile(PcscEid.ROOT_CERT_FILE_ID);
byte[] nationalRegitryCertData = pcscEidSpi.readFile(PcscEid.RRN_CERT_FILE_ID);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate authnCert = (X509Certificate) certificateFactory
.generateCertificate(new ByteArrayInputStream(authnCertData));
X509Certificate signCert = (X509Certificate) certificateFactory
.generateCertificate(new ByteArrayInputStream(signCertData));
X509Certificate citizenCaCert = (X509Certificate) certificateFactory
.generateCertificate(new ByteArrayInputStream(citizenCaCertData));
X509Certificate rootCaCert = (X509Certificate) certificateFactory
.generateCertificate(new ByteArrayInputStream(rootCaCertData));
X509Certificate nationalRegitryCert = (X509Certificate) certificateFactory
.generateCertificate(new ByteArrayInputStream(nationalRegitryCertData));
LOG.debug("authentication certificate: " + authnCert);
LOG.debug("signature certificate: " + signCert);
LOG.debug("national registry certificate: " + nationalRegitryCert);
LOG.debug("authn cert size: " + authnCertData.length);
LOG.debug("sign cert size: " + signCertData.length);
LOG.debug("citizen CA certificate: " + citizenCaCert);
LOG.debug("root CA certificate: " + rootCaCert);
LOG.debug("authn cert serial number: " + authnCert.getSerialNumber());
LOG.debug("authn certificate issuer: " + authnCert.getIssuerX500Principal());
File rootCaFile = File.createTempFile("test-root-ca-", ".pem");
FileWriter rootCaFileWriter = new FileWriter(rootCaFile);
PEMWriter rootCaPemWriter = new PEMWriter(rootCaFileWriter);
rootCaPemWriter.writeObject(rootCaCert);
rootCaPemWriter.close();
File citizenCaFile = File.createTempFile("test-citizen-ca-", ".pem");
FileWriter citizenCaFileWriter = new FileWriter(citizenCaFile);
PEMWriter citizenCaPemWriter = new PEMWriter(citizenCaFileWriter);
citizenCaPemWriter.writeObject(citizenCaCert);
citizenCaPemWriter.close();
pcscEidSpi.close();
LOG.debug("root ca file: " + rootCaFile.getAbsolutePath());
LOG.debug("citizen CA file: " + citizenCaFile.getAbsolutePath());
}
@Test
public void testReadIdentityFile() throws Exception {
PcscEid pcscEidSpi = new PcscEid(new TestView(), this.messages);
if (false == pcscEidSpi.isEidPresent()) {
LOG.debug("insert eID card");
pcscEidSpi.waitForEidPresent();
}
byte[] identityFile;
File tmpIdentitySignatureFile;
File tmpRRNCertFile;
File tmpPhotoFile;
File tmpAddressFile;
File tmpAddressSignatureFile;
try {
identityFile = pcscEidSpi.readFile(PcscEid.IDENTITY_FILE_ID);
byte[] identitySignatureData = pcscEidSpi.readFile(PcscEid.IDENTITY_SIGN_FILE_ID);
tmpIdentitySignatureFile = File.createTempFile("identity-sign-", ".der");
FileUtils.writeByteArrayToFile(tmpIdentitySignatureFile, identitySignatureData);
byte[] rrnCertData = pcscEidSpi.readFile(PcscEid.RRN_CERT_FILE_ID);
tmpRRNCertFile = File.createTempFile("rrn-cert-", ".der");
FileUtils.writeByteArrayToFile(tmpRRNCertFile, rrnCertData);
tmpPhotoFile = File.createTempFile("test-photo-", ".jpg");
FileUtils.writeByteArrayToFile(tmpPhotoFile, pcscEidSpi.readFile(PcscEid.PHOTO_FILE_ID));
tmpAddressFile = File.createTempFile("test-address-", ".tlv");
FileUtils.writeByteArrayToFile(tmpAddressFile, pcscEidSpi.readFile(PcscEid.ADDRESS_FILE_ID));
tmpAddressSignatureFile = File.createTempFile("test-address-sign-", ".der");
FileUtils.writeByteArrayToFile(tmpAddressSignatureFile, pcscEidSpi.readFile(PcscEid.ADDRESS_SIGN_FILE_ID));
} finally {
pcscEidSpi.close();
}
LOG.debug("identity file size: " + identityFile.length);
File tmpIdentityFile = File.createTempFile("identity-", ".tlv");
FileUtils.writeByteArrayToFile(tmpIdentityFile, identityFile);
Identity identity = TlvParser.parse(identityFile, Identity.class);
LOG.debug("DoB: " + identity.getDateOfBirth().getTime());
LOG.debug("document type: " + identity.getDocumentType());
LOG.debug("noble condition: " + identity.getNobleCondition());
LOG.debug("special status: " + identity.getSpecialStatus());
LOG.debug("duplicate: " + identity.getDuplicate());
LOG.debug("tmp identity file: " + tmpIdentityFile.getAbsolutePath());
LOG.debug("tmp identity signature file: " + tmpIdentitySignatureFile.getAbsolutePath());
LOG.debug("tmp RRN cert file: " + tmpRRNCertFile.getAbsolutePath());
LOG.debug("tmp photo file: " + tmpPhotoFile.getAbsolutePath());
LOG.debug("tmp address file: " + tmpAddressFile.getAbsolutePath());
LOG.debug("tmp address signature file: " + tmpAddressSignatureFile.getAbsolutePath());
}
@Test
public void testCardDataFile() throws Exception {
PcscEid pcscEid = new PcscEid(new TestView(), this.messages);
if (false == pcscEid.isEidPresent()) {
LOG.debug("insert eID card");
pcscEid.waitForEidPresent();
}
try {
CardChannel cardChannel = pcscEid.getCardChannel();
while (true) {
CommandAPDU getCardApdu = new CommandAPDU(0x80, 0xe4, 0x00, 0x00, 0x1c); // Le
// =
// 0x1c
ResponseAPDU responseApdu = cardChannel.transmit(getCardApdu);
if (0x9000 != responseApdu.getSW()) {
fail("SW error: " + Integer.toHexString(responseApdu.getSW()));
}
LOG.debug(Hex.encodeHexString(responseApdu.getData()));
}
} finally {
pcscEid.close();
}
}
@Test
public void testGetChallenge() throws Exception {
PcscEid pcscEid = new PcscEid(new TestView(), this.messages);
if (false == pcscEid.isEidPresent()) {
LOG.debug("insert eID card");
pcscEid.waitForEidPresent();
}
CardChannel cardChannel = pcscEid.getCardChannel();
int size = 256;
CommandAPDU getChallengeApdu = new CommandAPDU(0x00, 0x84, 0x00, 0x00, new byte[] {}, 0, 0, size);
ResponseAPDU responseApdu;
responseApdu = cardChannel.transmit(getChallengeApdu);
if (0x9000 != responseApdu.getSW()) {
fail("get challenge failure: " + Integer.toHexString(responseApdu.getSW()));
}
LOG.debug("challenge: " + Hex.encodeHexString(responseApdu.getData()));
assertEquals(size, responseApdu.getData().length);
pcscEid.close();
}
@Test
public void testGetChallengePcscEid() throws Exception {
PcscEid pcscEid = new PcscEid(new TestView(), this.messages);
if (false == pcscEid.isEidPresent()) {
LOG.debug("insert eID card");
pcscEid.waitForEidPresent();
}
int size = 20;
byte[] result = pcscEid.getChallenge(size);
assertEquals(size, result.length);
pcscEid.close();
}
/**
* Looking for a clean way to detect PPDU smart card readers.
*
* @throws Exception
*/
@Test
public void testDetectPPDU() throws Exception {
PcscEid pcscEid = new PcscEid(new TestView(), this.messages);
if (false == pcscEid.isEidPresent()) {
LOG.debug("insert eID card");
pcscEid.waitForEidPresent();
}
Card card = pcscEid.getCard();
int ioctl;
String osName = System.getProperty("os.name");
if (osName.startsWith("Windows")) {
ioctl = (0x31 << 16 | (3400) << 2);
} else {
ioctl = 0x42000D48;
}
byte[] features = card.transmitControlCommand(ioctl, new byte[0]);
if (0 == features.length) {
LOG.debug("no CCID reader");
return;
}
LOG.debug("feature list: " + new String(Hex.encodeHex(features)));
CCIDFeatures ccidFeatures = new CCIDFeatures(features);
for (int idx = 0; idx < 0x14; idx++) {
LOG.debug("has feature " + Integer.toHexString(idx) + " " + ccidFeatures.findFeature((byte) idx));
}
if (null != ccidFeatures.findFeature((byte) 0x12)) {
byte[] tlvFeatures = card.transmitControlCommand(ccidFeatures.findFeature((byte) 0x12), new byte[0]);
LOG.debug("TLV feature list: " + new String(Hex.encodeHex(tlvFeatures)));
FeatureGetTlvProperties featureGetTlvProperties = TlvParser.parse(tlvFeatures,
FeatureGetTlvProperties.class);
if (null != featureGetTlvProperties.bPPDUSupport) {
LOG.debug("PPDU support: " + featureGetTlvProperties.bPPDUSupport[0]);
}
if (null != featureGetTlvProperties.usbVendorId) {
LOG.debug("USB vendor id: " + Hex.encodeHexString(featureGetTlvProperties.usbVendorId));
}
if (null != featureGetTlvProperties.usbProductId) {
LOG.debug("USB product id: " + Hex.encodeHexString(featureGetTlvProperties.usbProductId));
}
}
}
public static class FeatureGetTlvProperties {
@TlvField(9)
public byte[] bPPDUSupport;
@TlvField(0x0b)
public byte[] usbVendorId;
@TlvField(0x0c)
public byte[] usbProductId;
}
private static class CCIDFeatures {
private final byte[] features;
public CCIDFeatures(byte[] features) {
this.features = features;
}
public Integer findFeature(byte featureTag) {
if (null == this.features) {
return null;
}
int idx = 0;
while (idx < this.features.length) {
byte tag = this.features[idx];
idx++;
idx++;
if (featureTag == tag) {
int feature = 0;
for (int count = 0; count < 3; count++) {
feature |= this.features[idx] & 0xff;
idx++;
feature <<= 8;
}
feature |= this.features[idx] & 0xff;
return feature;
}
idx += 4;
}
return null;
}
}
@Test
public void testCcid() throws Exception {
PcscEid pcscEid = new PcscEid(new TestView(), this.messages);
if (false == pcscEid.isEidPresent()) {
LOG.debug("insert eID card");
pcscEid.waitForEidPresent();
}
Card card = pcscEid.getCard();
// GET FEATURE LIST
byte[] features = card.transmitControlCommand(0x42000D48, new byte[0]);
if (0 == features.length) {
LOG.debug("no CCID reader");
return;
}
LOG.debug("feature list: " + new String(Hex.encodeHex(features)));
LOG.debug("feature verify pin direct: " + hasFeature(FEATURE_VERIFY_PIN_DIRECT_TAG, features));
Integer verifyPinControl = findFeature(FEATURE_VERIFY_PIN_DIRECT_TAG, features);
LOG.debug("VERIFY PIN control: 0x" + Integer.toHexString(verifyPinControl));
CardChannel cardChannel = pcscEid.getCardChannel();
CommandAPDU setApdu = new CommandAPDU(0x00, 0x22, 0x41, 0xB6,
new byte[] { 0x04, // length of following data
(byte) 0x80, // algo ref
0x01, // rsa pkcs#1
(byte) 0x84, // tag for private key ref
(byte) 0x82 });
ResponseAPDU responseApdu = cardChannel.transmit(setApdu);
if (0x9000 != responseApdu.getSW()) {
throw new RuntimeException("SELECT error");
}
byte[] verifyCommandData = createPINVerificationDataStructure();
byte[] result = card.transmitControlCommand(verifyPinControl, verifyCommandData);
responseApdu = new ResponseAPDU(result);
LOG.debug("status work: " + Integer.toHexString(responseApdu.getSW()));
if (0x9000 == responseApdu.getSW()) {
LOG.debug("status OK");
} else if (0x6401 == responseApdu.getSW()) {
LOG.debug("canceled by user");
} else if (0x6400 == responseApdu.getSW()) {
LOG.debug("timeout");
}
/*
* The other SW values are those from the VERIFY APDU itself.
*/
}
private byte[] createPINVerificationDataStructure() throws IOException {
ByteArrayOutputStream verifyCommand = new ByteArrayOutputStream();
verifyCommand.write(30); // bTimeOut
verifyCommand.write(30); // bTimeOut2
verifyCommand.write(0x89); // bmFormatString
/*
* bmFormatString. bit 7: 1 = system units are bytes
*
* bit 6-3: 1 = PIN position in APDU command after Lc, so just after the
* 0x20.
*
* bit 2: 0 = left justify data
*
* bit 1-0: 1 = BCD
*/
verifyCommand.write(0x47); // bmPINBlockString
/*
* bmPINBlockString
*
* bit 7-4: 4 = PIN length
*
* bit 3-0: 7 = PIN block size (7 times 0xff)
*/
verifyCommand.write(0x04); // bmPINLengthFormat
/*
* bmPINLengthFormat. weird... the values do not make any sense to me.
*
* bit 7-5: 0 = RFU
*
* bit 4: 0 = system units are bits
*
* bit 3-0: 4 = PIN length position in APDU
*/
verifyCommand.write(new byte[] { 0x04, 0x04 }); // wPINMaxExtraDigit
/*
* 0x04 = minimum PIN size in digit
*
* 0x04 = maximum PIN size in digit. This was 0x0C
*/
verifyCommand.write(0x02); // bEntryValidationCondition
/*
* 0x02 = validation key pressed. So the user must press the green
* button on his pinpad.
*/
verifyCommand.write(0x01); // bNumberMessage
/*
* 0x01 = message with index in bMsgIndex
*/
verifyCommand.write(new byte[] { 0x13, 0x08 }); // wLangId
/*
* 0x13, 0x08 = ?
*/
verifyCommand.write(0x00); // bMsgIndex
/*
* 0x00 = PIN insertion prompt
*/
verifyCommand.write(new byte[] { 0x00, 0x00, 0x00 }); // bTeoPrologue
/*
* bTeoPrologue : only significant for T=1 protocol.
*/
byte[] verifyApdu = new byte[] { 0x00, // CLA
0x20, // INS
0x00, // P1
0x01, // P2
0x08, // Lc = 8 bytes in command data
0x20, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF };
verifyCommand.write(verifyApdu.length & 0xff); // ulDataLength[0]
verifyCommand.write(0x00); // ulDataLength[1]
verifyCommand.write(0x00); // ulDataLength[2]
verifyCommand.write(0x00); // ulDataLength[3]
verifyCommand.write(verifyApdu); // abData
byte[] verifyCommandData = verifyCommand.toByteArray();
return verifyCommandData;
}
public static final byte FEATURE_VERIFY_PIN_DIRECT_TAG = 0x06;
private boolean hasFeature(byte featureTag, byte[] features) {
int idx = 0;
while (idx < features.length) {
byte tag = features[idx];
if (featureTag == tag) {
return true;
}
idx += 1 + 1 + 4;
}
return false;
}
private Integer findFeature(byte featureTag, byte[] features) {
int idx = 0;
while (idx < features.length) {
byte tag = features[idx];
idx++;
idx++;
if (featureTag == tag) {
int feature = 0;
for (int count = 0; count < 3; count++) {
feature |= features[idx] & 0xff;
idx++;
feature <<= 8;
}
feature |= features[idx] & 0xff;
return feature;
}
idx += 4;
}
return null;
}
@Test
public void testListReaders() throws Exception {
PcscEid pcscEid = new PcscEid(new TestView(), this.messages);
LOG.debug("reader list: " + pcscEid.getReaderList());
}
@Test
public void testBeIDPKIValidation() throws Exception {
PcscEid pcscEid = new PcscEid(new TestView(), this.messages);
if (false == pcscEid.isEidPresent()) {
LOG.debug("insert eID card");
pcscEid.waitForEidPresent();
}
try {
List<X509Certificate> certChain = pcscEid.getSignCertificateChain();
LOG.debug("certificate: " + certChain.get(0));
NetworkConfig networkConfig = new NetworkConfig("proxy.yourict.net", 8080);
TrustValidator trustValidator = BelgianTrustValidatorFactory
.createNonRepudiationTrustValidator(networkConfig);
trustValidator.isTrusted(certChain);
} finally {
pcscEid.close();
}
}
@Test
public void testBeIDPKIValidationCRLOnly() throws Exception {
PcscEid pcscEid = new PcscEid(new TestView(), this.messages);
if (false == pcscEid.isEidPresent()) {
LOG.debug("insert eID card");
pcscEid.waitForEidPresent();
}
List<X509Certificate> certChain;
try {
certChain = pcscEid.getSignCertificateChain();
} finally {
pcscEid.close();
}
LOG.debug("certificate: " + certChain.get(0));
NetworkConfig networkConfig = new NetworkConfig("proxy.yourict.net", 8080);
MemoryCertificateRepository memoryCertificateRepository = new MemoryCertificateRepository();
X509Certificate rootCaCertificate = loadCertificate("be/fedict/trust/belgiumrca.crt");
memoryCertificateRepository.addTrustPoint(rootCaCertificate);
X509Certificate rootCa2Certificate = loadCertificate("be/fedict/trust/belgiumrca2.crt");
memoryCertificateRepository.addTrustPoint(rootCa2Certificate);
RevocationData revocationData = new RevocationData();
TrustValidator trustValidator = new TrustValidator(memoryCertificateRepository);
trustValidator.setRevocationData(revocationData);
trustValidator.addTrustLinker(new PublicKeyTrustLinker());
OnlineCrlRepository crlRepository = new OnlineCrlRepository(networkConfig);
trustValidator.addTrustLinker(new CrlTrustLinker(crlRepository));
try {
trustValidator.isTrusted(certChain);
} catch (Exception e) {
LOG.warn("error: " + e.getMessage());
}
byte[] crlData = revocationData.getCrlRevocationData().get(1).getData();
CertificateList certificateList = CertificateList.getInstance(new ASN1InputStream(crlData).readObject());
X509Extensions crlExtensions = certificateList.getTBSCertList().getExtensions();
Enumeration<DERObjectIdentifier> oids = crlExtensions.oids();
while (oids.hasMoreElements()) {
LOG.debug("oid type: " + oids.nextElement().getId());
}
}
@Test
public void testPKIValidation() throws Exception {
PcscEid pcscEid = new PcscEid(new TestView(), this.messages);
if (false == pcscEid.isEidPresent()) {
LOG.debug("insert eID card");
pcscEid.waitForEidPresent();
}
try {
List<X509Certificate> certChain = pcscEid.getSignCertificateChain();
for (X509Certificate cert : certChain) {
LOG.debug("certificate: " + cert);
}
MemoryCertificateRepository certificateRepository = new MemoryCertificateRepository();
certificateRepository.addTrustPoint(certChain.get(certChain.size() - 1));
TrustValidator trustValidator = new TrustValidator(certificateRepository);
trustValidator.addTrustLinker(new PublicKeyTrustLinker());
NetworkConfig networkConfig = new NetworkConfig("proxy.yourict.net", 8080);
OnlineOcspRepository ocspRepository = new OnlineOcspRepository(networkConfig);
OnlineCrlRepository crlRepository = new OnlineCrlRepository(networkConfig);
CachedCrlRepository cachedCrlRepository = new CachedCrlRepository(crlRepository);
FallbackTrustLinker fallbackTrustLinker = new FallbackTrustLinker();
fallbackTrustLinker.addTrustLinker(new OcspTrustLinker(ocspRepository));
fallbackTrustLinker.addTrustLinker(new CrlTrustLinker(cachedCrlRepository));
trustValidator.addTrustLinker(fallbackTrustLinker);
trustValidator.isTrusted(certChain);
} finally {
pcscEid.close();
}
}
private static X509Certificate loadCertificate(String resourceName) {
LOG.debug("loading certificate: " + resourceName);
Thread currentThread = Thread.currentThread();
ClassLoader classLoader = currentThread.getContextClassLoader();
InputStream certificateInputStream = classLoader.getResourceAsStream(resourceName);
if (null == certificateInputStream) {
throw new IllegalArgumentException("resource not found: " + resourceName);
}
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate certificate = (X509Certificate) certificateFactory
.generateCertificate(certificateInputStream);
return certificate;
} catch (CertificateException e) {
throw new RuntimeException("X509 error: " + e.getMessage(), e);
}
}
}