package Chipcard::CTAPI; use 5.005; use strict; use warnings; use Carp; use Fcntl; require Exporter; use AutoLoader; our @ISA = qw(Exporter); our %EXPORT_TAGS = ( 'all' => [ qw( CT CTBCS_CLA CTBCS_DATA_STATUS_CARD CTBCS_DATA_STATUS_CARD_CONNECT CTBCS_DATA_STATUS_NOCARD CTBCS_INS_EJECT CTBCS_INS_REQUEST CTBCS_INS_RESET CTBCS_INS_STATUS CTBCS_MIN_COMMAND_SIZE CTBCS_MIN_RESPONSE_SIZE CTBCS_P1_CT_KERNEL CTBCS_P1_DISPLAY CTBCS_P1_INTERFACE1 CTBCS_P1_INTERFACE10 CTBCS_P1_INTERFACE11 CTBCS_P1_INTERFACE12 CTBCS_P1_INTERFACE13 CTBCS_P1_INTERFACE14 CTBCS_P1_INTERFACE2 CTBCS_P1_INTERFACE3 CTBCS_P1_INTERFACE4 CTBCS_P1_INTERFACE5 CTBCS_P1_INTERFACE6 CTBCS_P1_INTERFACE7 CTBCS_P1_INTERFACE8 CTBCS_P1_INTERFACE9 CTBCS_P1_KEYPAD CTBCS_P2_REQUEST_GET_ATR CTBCS_P2_REQUEST_GET_HIST CTBCS_P2_REQUEST_NO_RESP CTBCS_P2_RESET_GET_ATR CTBCS_P2_RESET_GET_HIST CTBCS_P2_RESET_NO_RESP CTBCS_P2_STATUS_ICC CTBCS_P2_STATUS_MANUFACTURER CTBCS_SW1_COMMAND_NOT_ALLOWED CTBCS_SW1_EJECT_NOT_REMOVED CTBCS_SW1_EJECT_OK CTBCS_SW1_EJECT_REMOVED CTBCS_SW1_ICC_ERROR CTBCS_SW1_OK CTBCS_SW1_REQUEST_ASYNC_OK CTBCS_SW1_REQUEST_CARD_PRESENT CTBCS_SW1_REQUEST_ERROR CTBCS_SW1_REQUEST_NO_CARD CTBCS_SW1_REQUEST_SYNC_OK CTBCS_SW1_REQUEST_TIMER_ERROR CTBCS_SW1_RESET_ASYNC_OK CTBCS_SW1_RESET_CT_OK CTBCS_SW1_RESET_ERROR CTBCS_SW1_RESET_SYNC_OK CTBCS_SW1_WRONG_CLA CTBCS_SW1_WRONG_INS CTBCS_SW1_WRONG_LENGTH CTBCS_SW1_WRONG_PARAM CTBCS_SW2_COMMAND_NOT_ALLOWED CTBCS_SW2_EJECT_NOT_REMOVED CTBCS_SW2_EJECT_OK CTBCS_SW2_EJECT_REMOVED CTBCS_SW2_ICC_ERROR CTBCS_SW2_OK CTBCS_SW2_REQUEST_ASYNC_OK CTBCS_SW2_REQUEST_CARD_PRESENT CTBCS_SW2_REQUEST_ERROR CTBCS_SW2_REQUEST_NO_CARD CTBCS_SW2_REQUEST_SYNC_OK CTBCS_SW2_REQUEST_TIMER_ERROR CTBCS_SW2_RESET_ASYNC_OK CTBCS_SW2_RESET_CT_OK CTBCS_SW2_RESET_ERROR CTBCS_SW2_RESET_SYNC_OK CTBCS_SW2_WRONG_CLA CTBCS_SW2_WRONG_INS CTBCS_SW2_WRONG_LENGTH CTBCS_SW2_WRONG_PARAM ERR_CT ERR_HTSI ERR_INVALID ERR_MEMORY ERR_TRANS HOST MAX_APDULEN OK PORT_COM1 PORT_COM2 PORT_COM3 PORT_COM4 PORT_LPT1 PORT_LPT2 PORT_Modem PORT_Printer ) ] ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); #=for full_export #our @EXPORT = qw( # CT # CTBCS_CLA # CTBCS_DATA_STATUS_CARD # CTBCS_DATA_STATUS_CARD_CONNECT # CTBCS_DATA_STATUS_NOCARD # CTBCS_INS_EJECT # CTBCS_INS_REQUEST # CTBCS_INS_RESET # CTBCS_INS_STATUS # CTBCS_MIN_COMMAND_SIZE # CTBCS_MIN_RESPONSE_SIZE # CTBCS_P1_CT_KERNEL # CTBCS_P1_DISPLAY # CTBCS_P1_INTERFACE1 # CTBCS_P1_INTERFACE10 # CTBCS_P1_INTERFACE11 # CTBCS_P1_INTERFACE12 # CTBCS_P1_INTERFACE13 # CTBCS_P1_INTERFACE14 # CTBCS_P1_INTERFACE2 # CTBCS_P1_INTERFACE3 # CTBCS_P1_INTERFACE4 # CTBCS_P1_INTERFACE5 # CTBCS_P1_INTERFACE6 # CTBCS_P1_INTERFACE7 # CTBCS_P1_INTERFACE8 # CTBCS_P1_INTERFACE9 # CTBCS_P1_KEYPAD # CTBCS_P2_REQUEST_GET_ATR # CTBCS_P2_REQUEST_GET_HIST # CTBCS_P2_REQUEST_NO_RESP # CTBCS_P2_RESET_GET_ATR # CTBCS_P2_RESET_GET_HIST # CTBCS_P2_RESET_NO_RESP # CTBCS_P2_STATUS_ICC # CTBCS_P2_STATUS_MANUFACTURER # CTBCS_SW1_COMMAND_NOT_ALLOWED # CTBCS_SW1_EJECT_NOT_REMOVED # CTBCS_SW1_EJECT_OK # CTBCS_SW1_EJECT_REMOVED # CTBCS_SW1_ICC_ERROR # CTBCS_SW1_OK # CTBCS_SW1_REQUEST_ASYNC_OK # CTBCS_SW1_REQUEST_CARD_PRESENT # CTBCS_SW1_REQUEST_ERROR # CTBCS_SW1_REQUEST_NO_CARD # CTBCS_SW1_REQUEST_SYNC_OK # CTBCS_SW1_REQUEST_TIMER_ERROR # CTBCS_SW1_RESET_ASYNC_OK # CTBCS_SW1_RESET_CT_OK # CTBCS_SW1_RESET_ERROR # CTBCS_SW1_RESET_SYNC_OK # CTBCS_SW1_WRONG_CLA # CTBCS_SW1_WRONG_INS # CTBCS_SW1_WRONG_LENGTH # CTBCS_SW1_WRONG_PARAM # CTBCS_SW2_COMMAND_NOT_ALLOWED # CTBCS_SW2_EJECT_NOT_REMOVED # CTBCS_SW2_EJECT_OK # CTBCS_SW2_EJECT_REMOVED # CTBCS_SW2_ICC_ERROR # CTBCS_SW2_OK # CTBCS_SW2_REQUEST_ASYNC_OK # CTBCS_SW2_REQUEST_CARD_PRESENT # CTBCS_SW2_REQUEST_ERROR # CTBCS_SW2_REQUEST_NO_CARD # CTBCS_SW2_REQUEST_SYNC_OK # CTBCS_SW2_REQUEST_TIMER_ERROR # CTBCS_SW2_RESET_ASYNC_OK # CTBCS_SW2_RESET_CT_OK # CTBCS_SW2_RESET_ERROR # CTBCS_SW2_RESET_SYNC_OK # CTBCS_SW2_WRONG_CLA # CTBCS_SW2_WRONG_INS # CTBCS_SW2_WRONG_LENGTH # CTBCS_SW2_WRONG_PARAM # ERR_CT # ERR_HTSI # ERR_INVALID # ERR_MEMORY # ERR_TRANS # HOST # MAX_APDULEN # OK # PORT_COM1 # PORT_COM2 # PORT_COM3 # PORT_COM4 # PORT_LPT1 # PORT_LPT2 # PORT_Modem # PORT_Printer #); #=cut our $VERSION = '0.2'; our $TRANSFER_BLOCK_SIZE = 128; sub AUTOLOAD { # This AUTOLOAD is used to 'autoload' constants from the constant() # XS function. my $constname; our $AUTOLOAD; ($constname = $AUTOLOAD) =~ s/.*:://; croak "&Chipcard::CTAPI::constant not defined" if $constname eq 'constant'; my ($error, $val) = constant($constname); if ($error) { croak $error; } { no strict 'refs'; # Fixed between 5.005_53 and 5.005_61 #XXX if ($] >= 5.00561) { #XXX *$AUTOLOAD = sub () { $val }; #XXX } #XXX else { *$AUTOLOAD = sub { $val }; #XXX } } goto &$AUTOLOAD; } require XSLoader; XSLoader::load('Chipcard::CTAPI', $VERSION); # Preloaded methods go here. # Automatically increased context for multiple connections to card terminals my $context = 0; # Standard constructor sub new { my $classname = shift; my $self = {}; bless($self, $classname); $self->_init(@_); return $self->open; } # Store additional key/value pairs sub _init { my $self = shift; if (@_) { my %extra = @_; @$self{keys %extra} = values %extra; } } sub setContext { my ($self, $context) = @_; $self->{'context'} = $context; } sub getContext { my ($self) = @_; return $self->{'context'}; } sub setInterface { my ($self, $interface) = @_; $self->{'interface'} = $interface; } sub getInterface { my ($self) = @_; return $self->{'interface'} ? # defaults to COM1 $self->{'interface'} : $self->setInterface(&PORT_COM1); } # Initialize the connection to the card terminal sub open { my $self = shift; my $result = CT_init($context, $self->getInterface()); unless ($result == &OK) { return; # returns undef if CT_init failed } $self->setContext($context); $context++; # Request card terminal information $self->requestTerminalStatus(); # Reset the card terminal $self->reset; return $self; } sub reset { my ($self) = @_; $self->saveATRandMemorySize(); $self->setMemorySize(0); $self->ejectICC(); $self->requestICC(); return $self->checkNinetyZero(); } sub setResult { my ($self, $result) = @_; $self->{'result'} = $result; } sub getResult { my ($self) = @_; return $self->{'result'}; } sub setResponseLength { my ($self, $l) = @_; $self->{'response_length'} = $l; } sub getResponseLength { my ($self) = @_; return $self->{'response_length'}; } sub setResponse { my ($self, $l) = @_; $self->{'response'} = $l; } sub getResponse { my ($self) = @_; return $self->{'response'}; } sub setDebugLevel { my ($self, $dl) = @_; $self->{'debug'} = $dl; } sub getDebugLevel { my ($self) = @_; return $self->{'debug'} ? $self->{'debug'} : 0; } # Set maximum width for dump output in chars sub setDumpWidth { my ($self, $w) = @_; $w = (int ($w / 3)) * 3; # round to multiple of 3 $self->{'dump_width'} = $w; } sub getDumpWidth { my ($self) = @_; return $self->{'dump_width'} ? $self->{'dump_width'} : 78; } # pretty prints a hexdump of $str's first $len bytes sub dumpCommunication { my ($self, $prefix, $str, $len) = @_; my $max_len = $self->getDumpWidth(); if (defined $prefix) { print "$prefix"; $max_len -= (int (((length $prefix) + 2) / 3)) * 3; } my @items = unpack("C" x $len, $str); my $format = "%02.02X " x $len; my $output = sprintf($format, @items); my $line_number = 0; while ($output =~ m/(.{1,$max_len})/g) { if ($line_number > 0) { print "$prefix"; } print $1 . "\n"; $line_number++; } return $line_number; } sub sendCommand { my ($self, $target, @cmd) = @_; my $cmdlen = @cmd; my $cmdstring = pack('C' x $cmdlen, @cmd); if ($self->getDebugLevel() > 0) { $self->dumpCommunication("--> ", $cmdstring, $cmdlen); } my ($result, $rsplen, $response) = CT_data( $self->getContext(), # context $target, # destination &HOST, # source $cmdlen, # command length $cmdstring, # command 255 # max response length ); $self->setResult($result); $self->setResponseLength($rsplen); $self->setResponse($response); if ($self->getDebugLevel() > 0) { $self->dumpCommunication("<-- ", $response, $rsplen); } return $result; } # Check whether the last two bytes from the most recent card terminal # response are 0x90 0x00, signalling success. # Returns: undef on error # 1 on success sub checkNinetyZero { my ($self) = @_; my $lr = $self->getResponse; return if ((length $lr) < 2); my $status_1 = substr($lr, -2, 1); my $status_2 = substr($lr, -1, 1); return unless ( ((ord $status_1) == 0x90) && ((ord $status_2) == 0x00) ); return 1; } sub checkSixtyTwoOne { my ($self) = @_; my $lr = $self->getResponse; return if ((length $lr) < 2); my $status_1 = substr($lr, -2, 1); my $status_2 = substr($lr, -1, 1); return unless ( ((ord $status_1) == 0x62) && ((ord $status_2) == 0x01) ); return 1; } sub checkSixtyTwoZero { my ($self) = @_; my $lr = $self->getResponse; return if ((length $lr) < 2); my $status_1 = substr($lr, -2, 1); my $status_2 = substr($lr, -1, 1); return unless ( ((ord $status_1) == 0x62) && ((ord $status_2) == 0x00) ); return 1; } sub close { my ($self) = @_; return CT_close($self->getContext()); } sub resetCardTerminal { my ($self) = @_; my @cmd = (0x20, 0x11, 0x01, 0x01, 0x03, 0xFF, 0x00, 0x00, 0x00); my $result = $self->sendCommand(&CT, @cmd); if ($result == &OK) { $self->setATR(); $self->calculateMemorySize(); } return $self->checkNinetyZero(); } sub requestCardStatus { my $self = shift; my @cmd = (&CTBCS_CLA, &CTBCS_INS_STATUS, &CTBCS_P1_CT_KERNEL, &CTBCS_P2_STATUS_ICC, 0); $self->sendCommand(&CT, @cmd); return $self->checkNinetyZero(); } sub requestTerminalStatus { my $self = shift; my @cmd = (&CTBCS_CLA, &CTBCS_INS_STATUS, &CTBCS_P1_CT_KERNEL, &CTBCS_P2_STATUS_MANUFACTURER, 0); $self->sendCommand(&CT, @cmd); if ($self->checkNinetyZero) { $self->extractTerminalInformation($self->getResponse); } return $self->checkNinetyZero(); } sub extractTerminalInformation { my ($self, $response) = @_; $self->{'ct_manufacturer'} = substr($response, 0, 5); $self->{'ct_model'} = substr($response, 5, 3); $self->{'ct_revision'} = substr($response, 10, 5); } sub getTerminalInformation { my ($self) = @_; return ($self->{'ct_manufacturer'}, $self->{'ct_model'}, $self->{'ct_revision'}); } sub cardInserted { my ($self) = @_; $self->requestCardStatus(); my $rsp = $self->getResponse(); return ((ord $rsp) > 0) ? 1 : 0; } sub setOldATRandMemorySize { my ($self) = @_; $self->setMemorySize($self->{'old_memory_size'}); $self->{'ATR'} = $self->{'old_ATR'}; } sub saveATRandMemorySize { my ($self) = @_; $self->{'old_memory_size'} = $self->getMemorySize(); $self->{'old_ATR'} = $self->getATR(); } sub requestICC { my ($self) = @_; my @cmd = (&CTBCS_CLA, &CTBCS_INS_REQUEST, &CTBCS_P1_INTERFACE1, &CTBCS_P2_REQUEST_GET_ATR, 0); my $result = $self->sendCommand(&CT, @cmd); if ($result == &OK) { # workaround for terminals which return 0x62 0x01 instead of # ATR 0x90 0x00 if the same card is still inserted, i.e. if # ejectICC() was ignored. if ($self->checkSixtyTwoOne) { $self->setOldATRandMemorySize; return 1; } $self->setATR(); $self->calculateMemorySize(); $self->extractProtocolInformation(); $self->extractStructureInformation(); } else { delete $self->{'ATR'}; $self->setMemorySize(0); } return $self->checkNinetyZero(); } sub setATR { my ($self) = @_; if ($self->getResponseLength() > 2) { my $lastResponse = substr($self->getResponse(), 0, $self->getResponseLength() - 2); $self->{'ATR'} = $lastResponse; } else { delete $self->{'ATR'}; } } sub getATR { my ($self) = @_; return $self->{'ATR'}; } sub setMemorySize { my ($self, $s) = @_; $self->{'memory_size'} = $s; } sub getMemorySize { my ($self) = @_; return $self->{'memory_size'} ? $self->{'memory_size'} : 0; } sub cardChanged { my ($self) = @_; $self->requestCardStatus(); $self->requestICC(); return 0 if ($self->checkSixtyTwoOne); # still same card return if ($self->checkSixtyTwoZero); # no card inserted return 1; # new card inserted } sub calculateMemorySize { my ($self) = @_; return unless (defined $self->getATR()); my $atr_1 = substr($self->getATR(), 1, 1); $atr_1 = unpack ("C", $atr_1); # memory size calculation as suggested by Heiko Abraham 2003/06/05 $self->setMemorySize( 1 << ((($atr_1 & 120) >> 3) + 6 ) * 1 << ( $atr_1 & 0x07) / 8); # my $i1 = ($atr_1 >> 3) & 0x07; # my $i2 = $atr_1 & 0x07; # my ($j1, $j2); # if ($i2 == 0) { # $j2 = 0; # } # elsif ($i2 == 1) { # $j2 = 1; # } # else { # $j2 = 1 << $i2; # } # if ($i1 == 0) { # $j1 = 0; # } # else { # $j1 = 64 << $i1; # } # if ($j1 && $j2) { # $self->setMemorySize(($j1 * $j2) / 8); # } # else { # $self->setMemorySize(0); # } } sub extractStructureInformation { my ($self) = @_; return unless (defined $self->getATR()); my $atr_0 = substr($self->getATR(), 0, 1); $atr_0 = unpack ("C", $atr_0); if (($atr_0 & 3) == 0) { $self->setStructureInformation('ISO'); } elsif(($atr_0 & 7) == 2) { $self->setStructureInformation('common'); } elsif(($atr_0 & 7) == 6) { $self->setStructureInformation('proprietary'); } else { $self->setStructureInformation('special'); } } sub setStructureInformation { my ($self, $s) = @_; $self->{'card_structure'} = $s; } sub getStructure { my ($self) = @_; return $self->{'card_structure'}; } sub extractProtocolInformation { my ($self) = @_; return unless (defined $self->getATR()); my $atr_0 = substr($self->getATR(), 0, 1); $atr_0 = unpack ("C", $atr_0); if (($atr_0 & 128) == 0) { $self->setProtocol('ISO'); } elsif (($atr_0 & 240) == 128) { $self->setProtocol('I2C'); } elsif (($atr_0 & 240) == 144) { $self->setProtocol('3W'); } elsif (($atr_0 & 240) == 160) { $self->setProtocol('2W'); } else { $self->setProtocol('unknown'); } } sub setProtocol { my ($self, $proto) = @_; $self->{'card_protocol'} = $proto; } sub getProtocol { my ($self) = @_; return $self->{'card_protocol'}; } sub selectFile { my ($self) = @_; my @apdu = (0x00, 0xA4, 0x00, 0x00, 0x02, 0x3F, 0x00); $self->sendCommand(0, @apdu); return $self->checkNinetyZero(); } sub ejectICC { my ($self) = @_; my @apdu = (0x20, 0x15, 0x01, 0x00, 0x00); $self->sendCommand(&CT, @apdu); return $self->checkNinetyZero(); } sub readChunk { my ($self, $address, $size, $offset) = @_; $self->selectFile() or return; $size &= 0xFF; # sanity check my @cmd = (0x00, 0xB0, ($address >> 8), ($address & 0xFF), $size); $self->sendCommand(0, @cmd); $self->extractData($offset); return $self->checkNinetyZero(); } sub setData { my ($self, $d) = @_; $self->{'data'} = $d; } sub getData { my ($self) = @_; return defined $self->{'data'} ? $self->{'data'} : ''; } sub getDataLength { my ($self) = @_; return length $self->getData(); } sub extractData { my ($self, $offset) = @_; my $data = substr($self->getResponse, 0, $self->getResponseLength - 2); my $data_length = length $data; my $olddata = $self->getData(); my $prefix = substr($olddata, 0, $offset); my $suffix; $suffix = substr($olddata, $offset + $data_length) if (length $olddata > $offset + $data_length); my $newdata; $newdata .= $prefix if (defined $prefix); $newdata .= $data if (defined $data); $newdata .= $suffix if (defined $suffix); $self->setData($newdata); } sub read { my ($self, $address, $size) = @_; my $offset = 0; $self->setData(''); if ($size > $self->getMemorySize) { $size = $self->getMemorySize; } return unless ($size); while ($size > $TRANSFER_BLOCK_SIZE) { $self->readChunk($address + $offset, $TRANSFER_BLOCK_SIZE, $offset) or return; $offset += $TRANSFER_BLOCK_SIZE; $size -= $TRANSFER_BLOCK_SIZE; } if ($size > 0) { $self->readChunk($address + $offset, $size, $offset) or return; } return $self->getDataLength; } # returns the index of the first occurence of $needle in our data # returns -1 if not found at all sub getIndex { my ($self, $needle) = @_; my $haystack = $self->getData; my $pos = 0; while ($haystack =~ m/(.)/g) { return $pos if ((ord $1) == $needle); $pos++; } return -1; } sub download { my ($self, $filename, $ascii_mode) = @_; $ascii_mode = 0 unless (defined $ascii_mode); sysopen(F, $filename, O_WRONLY|O_TRUNC|O_CREAT, 0600) or return; $self->read(0, $self->getMemorySize); unless($ascii_mode) { syswrite(F, $self->getData, $self->getMemorySize); } else { my $pos = $self->getIndex(0); syswrite(F, $self->getData, $pos) unless ($pos == -1); } CORE::close(F) or return; return 1; } # returns a single byte from the data storage sub getDataByte { my ($self, $index) = @_; return chr(0) unless (defined $self->{'data'}); my $c = length $self->{'data'} > $index ? substr($self->{'data'}, $index, 1) : chr(0); return $c; } sub writeChunk { my ($self, $address, $size, $offset) = @_; $self->selectFile or return; $size &= 0xFF; # sanity check for ctapi my @cmd = (0x00, 0xD6, $address >> 8, $address & 0xFF, $size); for (my $i=0; $i<$size; $i++) { push @cmd, ord $self->getDataByte($i + $offset); } $self->sendCommand(0, @cmd); $self->checkNinetyZero(); } sub write { my ($self, $address, $size) = @_; my $offset = 0; $size = $self->getMemorySize if ($size > $self->getMemorySize); return unless ($size); while ($size > $TRANSFER_BLOCK_SIZE) { $self->writeChunk($address + $offset, $TRANSFER_BLOCK_SIZE, $offset) or return; $offset += $TRANSFER_BLOCK_SIZE; $size -= $TRANSFER_BLOCK_SIZE; } if ($size > 0) { $self->writeChunk($address + $offset, $size, $offset) or return; } $self->checkNinetyZero(); } sub upload { my ($self, $filename) = @_; sysopen(F, $filename, O_RDONLY) or return 0; my $buf; my $num_read = sysread(F, $buf, $self->getMemorySize); return 0 unless ($num_read); CORE::close(F) or return 0; $self->setData(substr($buf, 0, $num_read)); $self->write(0, $num_read); return $self->checkNinetyZero(); } sub submitPIN { my ($self, $pin) = @_; my @cmd = (0x00, 0x20, 0x00, 0x00, length $pin); my @pin = split //, $pin; foreach my $p (@pin) { push @cmd, $p; } $self->sendCommand(0, @cmd); return $self->checkNinetyZero(); } sub changePIN { my ($self, $old_pin, $new_pin) = @_; my $both_pins = $old_pin . $new_pin; my @cmd = (0x00, 0x24, 0x00, 0x00, length $both_pins); my @pin = split //, $both_pins; foreach my $p (@pin) { push @cmd, $p; } $self->sendCommand(0, @cmd); return $self->checkNinetyZero(); } # Autoload methods go after =cut, and are processed by the autosplit program. 1; __END__ =pod =head1 NAME Chipcard::CTAPI - Perl module for communication with chipcard terminals =head1 SYNOPSIS use Chipcard::CTAPI; my $ct = new Chipcard::CTAPI('interface' => &Chipcard::CTAPI::PORT_COM1) or die "Can't communicate with card terminal"; my $memory_size = $ct->getMemorySize(); $ct->read(0, $memory_size) or die "Can't read data from card.\n"; $ct->dumpCommunication("Content: ", $ct->getData, $ct->getDataLength); my $content = "Hello, world!\n"; $ct->setData($content); $ct->write(0, $ct->getDataLength) or die "Can't write new content to card.\n"; $ct->close; =head1 ABSTRACT Chipcard::CTAPI enables Perl programs to communicate with chipcard terminals based on the low-level CTAPI driver. =head1 DESCRIPTION Using the CTAPI (card terminal application programming interface) is a simple yet powerful way to communicate with chipcard terminals. There are more advanced APIs available, like PC/SC, but in general they are not as easy and fast to set up as CTAPI. Especially when an application is not all about chipcards but just includes some features which can make use of them, CTAPI is often the best way to go as it implies less overhead for the end user. Chipcard::CTAPI is a Perl module which provides direct access to the low-level CTAPI functions (which are, in fact, only three), but focusses on a couple of convenience methods for reading and writing memory cards. =head1 METHODS This description of methods is sorted in the order you probably will want to use them. =over 4 =item B Creates a new Chipcard::CTAPI object. You must pass at least the I option which specifies the physical port your card terminal is attached to. Example: my $ct = new Chipcard::CTAPI('interface' => &Chipcard::CTAPI::PORT_COM1); A list of all possible PORT-constants can be looked up in CTAPI.pm or your local ctapi.h . Note that there's no port numbers defined for USB card drivers; these are likely to use port numbers greater then 32768. See your card terminal's CTAPI documentation for details. B returns B if the communication with the card terminal can't be established. If you can't get it to work, please try other programs based on CTAPI to check whether you've got a hardware problem. =item B Checks whether there's currently a card inserted into the reader. Returns 1 if a card is available, 0 otherwise. =item B Returns the memory size of the currently inserted card in bytes or 0 if there is no card inserted. The size is actually calculated from the second byte of the card's ATR string which is automatically fetched when using B or B. Example: my $memory_size = $ct->getMemorySize(); =item B (address, size) Reads I bytes from the currently inserted card's memory, starting at I
. Returns the number of bytes actually read. The fetched data can be accessed using the B method. Example how to read you card's whole memory at once: my $num_bytes_read = $ct->read(0, $ct->getMemorySize); =item B Returns the card memory retrieved by the last B method call as string. This also works for binary data. Example: my $data = $ct->getData(); An empty string is returned if there is no data available. =item B Returns the length of the most recently set or fetched data in bytes. =item B (string) Stores data in the object's internal data buffer; this is required before data can be written to the card. Example: $ct->setData("Hello, world!\n"); Be aware that no size checks are done here. If the length of the data you set exceeds the card's memory, it will be truncated when writing it onto the card. =item B (address, size) Writes B bytes from the currently buffered data, which for example was set using B, onto the card, starting at B
. Returns 1 if the write access was successful, B otherwise. Notes: If the the sum of B
and B is greater than your card's memory, the data will be truncated. If B is greater than the length of the currently buffered data, the trailing (B - B) bytes will be filled up with null-bytes (chr(0)). Example: $ct->write(0, $ct->getMemorySize); =item B (filename, [ascii-mode]) Fetches the currently inserted card's whole memory and stores it in a file called B. If the optionally given second parameter is a true value, only the first I bytes of the card's memory will be stored in the file, where I is the index of the first null-byte. Returns 1 on success, B otherwise. Example 1: $ct->download("dump.bin") or die "Can't dump the card.\n"; Example 2: $ct->setData("Hello, world!\n\000"); $ct->write(0, $ct->getDataLength); $ct->download("message.txt", 1); =item B (filename) Stores the content of the given file in the card's memory. If the file is larger than the card's memory, it will be truncated. If it is smaller, the last (getMemorySize - filesize) bytes of the card's memory will be left untouched. Example: $ct->upload("card_memory_image.bin"); Returns 1 on success, B otherwise. =item B Resets the chipcard. Use this whenever you expect a different card to be inserted in the terminal meanwhile. Returns 1 if there is a card inserted, B otherwise. Notes: This internally calls B and B and evaluates the currently inserted card's ATR string (see B), if present. It thus is required that you call B whenever the user has inserted a new chipcard in order to get correct data from methods like B. If you want to auto-detect card changes, please see B. =item B Checks whether the user has exchanged the card in the reader. Returns 1 if there's a new card in the reader meanwhile. Returns 0 if still the same card is inserted. Returns B if there's no card inserted now. Note: if the user removes his card and inserts the same card again, the result is the same as with a new card. =item B Reset the card terminal. CTBCS specs say that applications should use this only after a communication error between the application and the card terminal. Avoid using it; B will suffice in most cases. Returns 1 if successful, B otherwise. =item B Returns a triple with information about the card terminal hardware: my ($manufacturer, $model, $revision) = $ct->getTerminalInformation(); =item B Closes the communication with the card terminal. Returns B if successful. =back Methods for direct communication with the card terminal: =over 4 =item B ($destination, @command) Sends the command consisting of the sequence of bytes given in B to the given B. The B usually is either the constant B for commands to the card terminal itself or B<0> for reading and writing the card's memory. Returns the result of the underlying call to B, which should be 0 on success. =item B Returns the card terminal's response to the latest command sent by B. This usually consist of any requested data, followed by two status bytes. Very often, the status bytes 0x90 0x00 signal success, see B. =item B Returns the length of the card terminal's response to the latest command in bytes. =item B Verifies that the last response from the card terminal ended with 0x90 0x00, which signals the success of the most recent operation. Returns 1 if the last response ended in 0x90 0x00, B otherwise. =item B Checks whether the last response from the card terminal ended with 0x62 0x01. That's the response one usually gets after a call to B if the same card is still inserted. Thus, if you want to check whether still the same card is inserted, you could use something like this: $ct->requestCardStatus(); $ct->requestICC(); my $is_same_card = $ct->checkSixtyTwoOne(); die "You didn't insert a new card!\n" if ($is_same_card); Comparable functionality is implemented by B. B returns 1 if the last response ended in 0x62 0x01, B otherwise. =item B Sends the "select file" command. This is automatically done by the various read/write methods provided by Chipcard::CTAPI, so you should only need to use it if you send read/write requests manually. Returns 1 on success, B otherwise. =item B Sends the "request ICC" command. Fetches the card's ATR (answer to reset) string and calculates the card's memory based on it. Returns 1 on success, B otherwise. =item B Sends the "eject ICC" command. Disconnects the currently inserted card from the card terminal. If the hardware supports it, the card is also ejected. Returns 1 on success, B otherwise. =item B Sends the "get status" command to the terminal, requesting information about whether currently a card is inserted and connected or not. The method B is a more convenient way of figuring that out. Returns 1 on success, B otherwise. =item B Sends the "get status" command to the terminal, this time requesting information about the card terminal itself. This command is automatically issued on initialization and its result is available through B. Returns 1 on success, B otherwise. =item B Returns the currently inserted card's ATR string which can be used to identify the card. Returns B if there's no card inserted or if there is a new card inserted but B has not been called yet. =item B Returns the protocol used by the currently inserted card. Possible values: "ISO", "I2C", "3W", "2W", "unknown". Returns B if there is no card inserted or a new card is inserted but B has not been called yet. =item B Returns the card's structure ident. Possible values: "ISO", "common", "proprietary", "special". Returns B if there is no card inserted or a new card is inserted but B has not been called yet. =back Methods useful for debugging: =over 4 =item B (prefix, data, length) Pretty-prints a hexdump of the first I bytes of I on STDOUT. Each line of output is prefixed with I. If the debugging mode is turned on, all communication with the card terminal will be dumped. Returns the number of lines printed. =item B Sets the maximum line width for B output. Defaults to 78. As each byte is printed as a hexadecimal value, followed by a blank (0x20), the should be a multiple of 3. Rounding to a multiple of 3 is automatically done. =item B (ascii-value) Returns the first position in the currently buffered data matching the given value. Returns -1 if there's no match at all. Examples: my $pos = $ct->getIndex(ord "\n"); # Returns position of first \n my $first_line = substr($self->getData, 0, $ct->getIndex(ord "\n")); =item B (level) Sets the debug level. Currently only used to turn debugging on/off. When turned on (i.e., the level is a true value), all bytes sent to and received from the card terminal via B will be pretty-printed to STDOUT using B. =back Chipcard::CTAPI also provides direct access to the low-level CTAPI functions: =over 4 =item B (context, port) Initializes the communication with the card terminal. B is a unique short integer (0-255) which must be used on all subsequent calls to B and B. B defines the physical interface on which the card reader is attached to. Returns the constant B on success. =item B (context, dest, src, cmd_len, cmd, max_rsp_len) Sends the B, which is B bytes long, over the communication channel identified by B from B to B and expects an answer which is at most B bytes long. The source usually is set to the constant B, whereas the destination is either the card terminal (constant B) or 0. A call to CT_data returns a triple: my ($result, $response_length, $response) = CT_data(...) B is the numerical return value from the underlying CT_data() call and should be B in case of success. The last two bytes of B represent status information. Often, the sequence 0x90 0x00 signals successful completion of the last requested operation. The B method can be used to verify whether the last response has ended with that sequence. =item B (context) Closes the connection identified by B. Returns B on success. =back Please consult the CTAPI documentation for more details. The following methods are B. Use them strictly at your own risc. They are believed to work, if at all, only with B<3W> cards (see B). =over 4 =item B (pin) Tries to unlock a PIN-protected card with the given PIN. WARNING: submitting a wrong PIN or the right PIN in a wrong way multiple times will render your card useless! As the way PIN verification is implemented in Chipcard::CTAPI might be wrong for your hardware, B try again if your first attempt fails! Example: $ct->submitPIN("000") or die "Sorry, wrong PIN!\n"; =item B (oldpin, newpin) Tries to change the PIN of a PIN-protected card. Both the old and the new PIN must be passed (in this order). Same warnings as for B apply here, too. Example: $ct->changePIN("123", "456") or die "Changing PIN failed!\n"; =back =head1 OPTIONS The following options can be passed when creating a new Chipcard::CTAPI object using the method B: =over 4 =item B Specifies the physical port your card terminal is attached to. The following constants can be used: PORT_COM1 .. PORT_COM4, PORT_LPT1 and PORT_LPT2. For USB devices, your driver probably has assigned numbers greater than 32768. Please consult its documentation. =item B Used to turn on/off debugging. If set to a true value, each read and write access to the card terminal for which B is used will be printed on STDOUT using B. =back =head1 EXAMPLES The Chipcard::CTAPI distribution archive comes with a couple of demo applications which you should have a look at. Some simple quick-start examples follow. Establish communication with a card terminal on COM2: my $ct = new Chipcard::CTAPI(interface => &Chipcard::CTAPI::PORT_COM2) or die "Can't initialize card terminal on COM2!\n"; Wait till the user has inserted a card with at least 2kb memory: my $try = 1; while($ct->getMemorySize < 2048) { if ($try == 1) { print "Please insert a memory card with at least 2k capacity."; } elsif ($try < 10) { print "."; } else { die "Timeout.\n"; } sleep 1; $ct->reset; $try++; } Read the first 512 byte from the card's memory: $ct->read(0, 512); if ($ct->getDataLength == 512) { # do something with the data my $data = $ct->getData; # ... } else { # error treatment } Read the bytes 1500 - 2000 from the card's memory: $ct->read(1500, 500); # read 500 bytes, starting at address 1500 Read the whole memory: $ct->read(0, $ct->getMemorySize); Read the whole memory and store it in a file named card.bin : $ct->download("card.bin"); Store "Hello, world" at the beginning of the card's memory: $ct->setData("Hello, world"); $ct->write(0, $ct->getDataLength); Store "Hello, world" at the card's memory address 1000: $ct->setData("Hello, world"); $ct->write(1000, $ct->getDataLength); Erase a card's memory by overwriting it with null-bytes: $ct->setData(''); $ct->write(0, $ct->getMemorySize); Erase a card's memory by overwriting each byte with a '?': my $mem_size = $ct->getMemorySize; $ct->setData('?' x $mem_size); $ct->write(0, $ct->getMemorySize); Check whether there's a card inserted at the moment: die "No card, no fun.\n" unless ($ct->cardInserted); Handling card changes: print "Please insert another card...\n"; sleep 10; # wait for the user to do so ... $ct->reset; # this initializes the new card print "New card's capacity: " . $ct->getMemorySize . "bytes.\n"; Checking whether the card has been exchanged: my $change = $ct->cardChanged(); if (!defined $change) { print "No card inserted now!\n"; } elsif ($change == 0) { print "Still same card inserted!\n"; } else { print "New card inserted!\n"; $ct->reset(); # must do! print "Capacity of new card: " . $ct->getMemorySize . "\n"; } =head1 BUGS Chipcard::CTAPI has currently only been tested with Towitoko card terminals. Please report problems with other terminals and ideas for solving them. =head1 TODO Provide convenience methods for handling processor cards (real smartcards). =head1 SEE ALSO =over 4 =item PCSC::Lite A Perl module to communicate with card terminals using PC/SC-Lite. Requires a running PCSC daemon on your system. =item ctapi(3) Vendor-specific CTAPI library documentation. =back =head1 AUTHOR Wolfgang Hommel (wolf (at) code-wizards.com) =head1 COPYRIGHT AND LICENSE Copyright 2003 Wolfgang Hommel This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut