Smart Card Testing Framework

Testing smart card applications: unit testing applets, integration testing with simulators, and EMV conformance validation.

| 4 min read

Smart Card Testing Framework

Rigorous testing of smart card applets and systems spans three distinct layers: functional APDUAPDUProtocolCommunication unit between card and reader.Click to view → testing, negative and fuzz testing, and formal compliance test suites required by certification schemes. A well-structured test framework automates all three layers and integrates with the development pipeline.

Use the APDU Builder to prototype commands before encoding them in automated test cases.

Testing Architecture

┌─────────────────────────────────────────────┐
│  Compliance Test Suites (EMVCo, GP, ICAO)   │  ← Certification
├─────────────────────────────────────────────┤
│  Fuzz / Negative Testing                    │  ← Security
├─────────────────────────────────────────────┤
│  Functional APDU Test Suite                 │  ← Correctness
├─────────────────────────────────────────────┤
│  PC/SC Middleware / Simulator               │  ← Infrastructure
└─────────────────────────────────────────────┘

Functional APDU Testing

The most portable approach is to define test vectors as structured data and execute them via any PC/SC connection. A minimal Python framework using pyscard:

import pytest
from smartcard.System import readers
from smartcard.util import toBytes, toHexString

@pytest.fixture(scope="session")
def card_conn():
    r = readers()[0]
    conn = r.createConnection()
    conn.connect()
    yield conn
    conn.disconnect()

class TestSelectApplet:
    SELECT_AID = toBytes("00 A4 04 00 07 D2 76 00 00 85 01 01 00")

    def test_select_returns_9000(self, card_conn):
        data, sw1, sw2 = card_conn.transmit(self.SELECT_AID)
        assert (sw1, sw2) == (0x90, 0x00), \
            f"Expected 9000, got {sw1:02X}{sw2:02X}"

    def test_fci_contains_aid(self, card_conn):
        data, sw1, sw2 = card_conn.transmit(self.SELECT_AID)
        assert 0xD2 in data  # AID byte present in FCI

For more complex state machines, use a fixture that resets card state between tests via a card reset or session re-select.

Test Vector Tables

Organise APDU tests as parameter tables for systematic coverage:

Command CLA INS P1 P2 Lc Data Expected SW
SELECT (valid AIDAIDProtocolUnique identifier for card applications.Click to view →) 00 A4 04 00 07 D2760000850101 9000
SELECT (wrong AID) 00 A4 04 00 05 FFFFFFFFFF 6A82
VERIFY PIN (correct) 00 20 00 80 04 31323334 9000
VERIFY PIN (wrong) 00 20 00 80 04 FFFFFFFF 63C2
GET DATA (empty) 00 CA 00 6F 00 9000 or 6A88

Encoding these in pytest.mark.parametrize allows running the full matrix with a single command.

Negative and Boundary Testing

Security-critical commands must be tested for rejection of malformed input:

@pytest.mark.parametrize("lc,data,expected_sw", [
    (0x01, [0xFF], 0x6700),          # Wrong Lc for VERIFY
    (0x10, [0xFF]*16, 0x6700),       # Lc too long
    (0x04, [0x31,0x32,0x33], 0x6700),# Lc/data length mismatch
])
def test_verify_rejects_malformed(card_conn, lc, data, expected_sw):
    cmd = [0x00, 0x20, 0x00, 0x80, lc] + data
    _, sw1, sw2 = card_conn.transmit(cmd)
    assert (sw1 << 8 | sw2) == expected_sw

Fuzz Testing

For security evaluation, randomised APDU fuzzing discovers unexpected behaviour:

import random

def fuzz_apdu(card_conn, iterations=1000):
    findings = []
    for _ in range(iterations):
        cla = random.choice([0x00, 0x80, 0x84, 0xC0])
        ins = random.randint(0, 0xFF)
        p1  = random.randint(0, 0xFF)
        p2  = random.randint(0, 0xFF)
        lc  = random.randint(0, 0x10)
        data = [random.randint(0, 0xFF) for _ in range(lc)]
        cmd = [cla, ins, p1, p2, lc] + data
        try:
            _, sw1, sw2 = card_conn.transmit(cmd)
            sw = (sw1 << 8 | sw2)
            if sw == 0x9000:  # Unexpected success
                findings.append((cmd, sw))
            elif sw not in (0x6700, 0x6A86, 0x6D00, 0x6E00, 0x6F00):
                findings.append((cmd, sw))  # Unusual SW
        except Exception as e:
            findings.append((cmd, str(e)))
    return findings

Document all findings; a 9000 response to a random INS byte may indicate an undocumented command — a security risk worth investigating.

Compliance Test Suites

Scheme Suite Tool
EMVEMVApplicationGlobal chip payment card standard.Click to view → EMVCoEMVCoStandardBody managing EMV payment standards.Click to view → Level 1/2 Test Suite Testhouse, UL Labs
GlobalPlatformGlobalPlatformSoftwareCard application management standard.Click to view → GP Compliance Test Tool GlobalPlatform member tools
ICAO ePassportePassportApplicationPassport with embedded contactless chip.Click to view → ICAO Test Specifications (Doc 9303 Part 13) Keesing RFI, Trustpoint
Common CriteriaCommon CriteriaSecurityInternational IT security evaluation standard.Click to view → Sponsor's test cases in Security Target Per-scheme tooling
FIPS 140FIPS 140ComplianceUS government cryptographic module security standard.Click to view → CMVP ACVTS (algorithm validation) NIST ACVTS

EMVCo Level 1 compliance requires formal submission to an EMVCo-accredited test laboratory; Level 2 (kernel) compliance is tested by the card and terminal vendor using EMVCo's published test cases.

See the Smart Card Debugging Guide for APDU trace analysis, and the Dev Environment Setup Guide for reader and simulator configuration.

자주 묻는 질문

Our guides cover a range of experience levels. Getting Started guides introduce smart card fundamentals. Security guides address Common Criteria certification and key management. Programming guides target developers working with APDU commands, JavaCard applets, and GlobalPlatform card management.