mardi 2 août 2016

PCSCLite, c'est quand même un peu de la daube

De retour sur le blog après plusieurs jours passés à expérimenter.

La joie qui m'a animé suite à mon premier programme m'a vite quitté. En effet, j'ai été confronté à une série de problèmes plus hardus à résoudre les uns que les autres.

Tout d'abord je me suis rendu compte que la boucle retour de mon programme faisait planter ma carte à puce. Ce que j'appelle la boucle retour c'est le retour à l'attente de l'APDU suivant lorsqu'on a traité le précédent. Gscriptor ne m'affichait la réponse de la carte que lorsque la boucle retour était oubliée. Aussi incroyable que cela puisse paraitre, mon programme ne marchait qu'à partir du moment où le code partait dans le décor ! Pas coton de trouver d'où ça venait.

Autre problème : je me suis rendu compte que ma carte rebootait entre 2 APDU. Pour une carte censée mémoriser entre 2 APDU qu'un code PIN a été entré, c'est moyen.

J'ai fini par trouver que tous mes problèmes venaient de la routine RECEIVE pompée sur la carte de déplombage de la RAI : elle n'avait pas les bonnes tempo. J'ai réussi à trouver les bonnes en analysant les tempos de la routine SEND. A ce demander si ce programme a bien permis de regarder la RAI un jour.

Bref, me voilà remis en selle puisque la correction de cette routine a non seulement fait marcher ma boucle retour, mais a aussi fait en sorte que la puce ne rebootait plus entre 2 APDU.

C'est à ce moment là qu'a surgit un problème aussi pénible : je me suis rendu compte que la couche PCSCLite filtrait à mort les réponses de ma carte à puce. En fait, on pourrait croire que PCSC n'est qu'une couche permettant d'accéder aux cartes à puce sans se soucier de la gestion de l'USB. Que nenni. PCSCLite se croit investi de la mission de vérifier que les échanges entre le PC et la carte à puce se font bien selon les standards internationaux en vigueur. Et il n'hésite pas à enlever des octets réponses s'ils ne lui plaisent pas, voire à rajouter dans la réponse de la carte un octet à lui !

En fait, on n'est même pas libre d'employer le protocole de communication que l'on veut avec sa propre carte !

J'ai vite compris que cette daube allait me poser un très gros problème. D'autant qu'il ne parait pas possible de bypasser ce comportement pénible. Je ne voyais que 2 solutions : soit bûcher les standards de communication internationaux (ce que je ne souhaitais pas faire initialement), soit se contenter d'échanges avec 2 octets de réponse de la carte par APDU seulement. En effet, PCSCLite semble laisser passer les octets SW1 et SW2 des réponses des cartes, mais commence à filtrer dès que ces octets sont accompagnés de data. ET quand je dis filtrer je suis gentil : les 3/4 du temps la couche PCSCLite informe le programme utilisateur que, de son point de vue, il y a une erreur de transmission et lui transmet ... rien (mais de quoi je me mêle ?).

Le fait de tenter de coller aux standards internationaux n'a rien donné de bon : malgré mes efforts, PCSCLite ne semblait jamais assez satisfait et continuait à filtrer.

Pour s'en sortir, restait donc la solution de ne communiquer les réponses que sur les 2 octets SW1 et SW2. Le principe est d'au lieu d'envoyer un APDU qui entraine une réponse dans laquelle il y a 32 octets, on envoie 32 APDU qui entrainent chacun un octet de réponse. On a ainsi quand même les 32 octets réponse qu'on cherche, c'est un peu plus long, mais au moins on met un gros à cette daube de PCSCLite. C'est donc la solution que j'ai choisie et mis en oeuvre.

Même en faisant comme ça, il fallait tout de même faire attention. Si SW1 vaut 0x60, par exemple, PCSCLite filtrait SW1 et SW2 ! Quelle chienlit. J'ai fini par trouver des couples SW1/SW2 qui passait sans problème la couche PCSCLite, et j'ai pu bâtir un protocole de communication avec ça.

Que d'énergie perdue et d'énergie dépensée à cause de PCSCLite. Mais bon, on final, ça marche.

Je me suis ensuite heurté au fait que, si la lecture de l'EEPROM interne du PIC ne me posait pas de problème, impossible de réussir à y écrire. Là aussi, des heures et des heures à chercher d'où le problème pouvait venir, pour finir par enfin découvrir que le problème venait d'une variable temporaire commune entre les routines EEPROM_WRITE et SEND. La séparation des variables a miraculeusement permis l'écriture dans l'EEPROM.

J'avais absolument besoin d'une routine EEPROM_WRITE qui marche pour stocker le nombre d'essais restant pour le code PIN et pour la mise à jour de la clé en EEPROM via un APDU.

Une fois tous ces problèmes résolus, j'ai pu écrire un programme gérant complètement 3 codes PIN (1 code PIN utilisateur, 1 code PIN administrateur, 1 code PIN de compromission). Ce programme, dont je ne suis pas peu fier car il m'a fallu des heures et des heures pour le debugger, est fourni ci-dessous.

J'ai pu également tester la protection du PIC activée directement dans l'injecteur InfinityUSB unlimited. Ca marche nickel. Cela se paramètre au moment de l'injection du programme dans le PIC et cela entraine le fait que la lecture du PIC ainsi verrouillé ne ramène que des 00 (pour le code du programme et pour la zone EEPROM). La clé contenue dans la puce est donc bien à l'abri avec ce mécanisme.

En fait, je suis allé bien plus loin que tout cela puisque j'ai quasiment finalisé le code la puce, qui contient maintenant beaucoup plus que le programme de gestion des 3 codes PIN présenté ci-dessous. Je développerai le produit fini dans un prochain article, probablement fin août après mes vacances.


LIST P=16F876, F=INHX8M
include "P16F876.INC"
__CONFIG _BODEN_OFF & _CP_OFF & _WRT_ENABLE_OFF & _PWRTE_OFF & _WDT_OFF & _XT_OSC & _DEBUG_OFF & _CPD_OFF & _LVP_OFF


;----------------------Zone de RAM --------------------------
ORG 0x60
IODATA RES 1
BIT_COUNT RES 1
PARITY RES 1
TEMPO RES 1
TEMP RES 1
CLA RES 1
INS RES 1
P1 RES 1
P2 RES 1
LE RES 1
RANDOM RES 1
PIN1GIVEN RES 1
PIN2GIVEN RES 1

ORG 0x160
EEADRW RES 1

;------------------------Zone de programme ----------------
ORG 0x0000
GOTO INIT

ORG 0x0004
RETFIE

SEND MOVWF IODATA
MOVLW 0x32
CALL WAIT2
BCF 0x6,7
MOVLW 0x7f
BSF STATUS,RP0
MOVWF TRISB
BCF STATUS,RP0
CLRF PARITY
MOVLW 0x8
MOVWF BIT_COUNT
BOUCLE_SEND
CALL WAIT1
RRF IODATA,F
RRF IODATA,W
ANDLW 0x80
XORWF PARITY,F
XORWF 0x6,W
XORWF 0x6,F
DECFSZ BIT_COUNT,F
GOTO BOUCLE_SEND
CALL WAIT1
MOVF PARITY,W
XORWF 0x6,W
XORWF 0x6,F
CALL WAIT1
MOVLW 0xff
BSF STATUS,RP0
MOVWF TRISB
BCF STATUS,RP0
MOVLW 0x3e
GOTO WAIT2

RECEIVE BTFSC 0x6,7
GOTO RECEIVE
MOVLW 0x2d
CALL WAIT2
MOVLW 0x9
MOVWF BIT_COUNT
BOUCLE_RECEIVE
BCF 0x3,0
BTFSC 0x6,7
BSF 0x3,0
RRF IODATA,F
CALL WAIT1
NOP
NOP
DECFSZ BIT_COUNT,F
GOTO BOUCLE_RECEIVE
RLF IODATA,F
CALL WAIT1
CALL WAIT1
MOVF IODATA,W
RETURN

WAIT1 MOVLW 0x1a
WAIT2 MOVWF TEMPO
BWAIT DECFSZ TEMPO,F
GOTO BWAIT
RETLW 0x0

EEPROM_READ ; mettre au préalable l'adresse à lire dans W
BSF STATUS,RP1
BCF STATUS,RP0
MOVWF EEADR
BSF STATUS,RP0
BCF EECON1,EEPGD
BSF EECON1,RD
BCF STATUS,RP0
MOVF EEDATA,W
BCF STATUS,RP1
RETURN
EEPROM_WRITE
BSF STATUS,RP1 ; banque 3
BSF STATUS,RP0
BTFSC EECON1,WR
GOTO $-1
BCF STATUS,RP0 ; banque 2
MOVWF EEDATA
MOVF EEADRW,W
MOVWF EEADR
BSF STATUS,RP0 ; banque 3
BCF INTCON,GIE
BSF EECON1,WREN
MOVLW 0x55
MOVWF EECON2
MOVLW 0xAA
MOVWF EECON2
BSF EECON1,WR
;BSF INTCON,GIE
BCF EECON1,WREN
BCF STATUS,RP0 ; banque 0
BCF STATUS,RP1
GOTO BOUCLE

SET_PIN_3
BSF STATUS,RP1 ; banque 2
MOVLW PINC
MOVWF EEADRW
MOVLW 0x3
CALL EEPROM_WRITE
GOTO BOUCLE
SET_PIN_2
BSF STATUS,RP1 ; banque 2
MOVLW PINC
MOVWF EEADRW
MOVLW 0x2
CALL EEPROM_WRITE
GOTO BOUCLE
SET_PIN_1
BSF STATUS,RP1 ; banque 2
MOVLW PINC
MOVWF EEADRW
MOVLW 0x1
CALL EEPROM_WRITE
GOTO BOUCLE
LOCK_CARD
BSF STATUS,RP1 ; banque 2
MOVLW LOCK
MOVWF EEADRW
MOVLW 0x1
CALL EEPROM_WRITE
GOTO BOUCLE

BAD_CLASS
MOVLW 0x6E
CALL SEND
MOVLW 0x00
CALL SEND
GOTO BOUCLE

TESTE_PIN
; Test par rapport au PIN3
MOVLW PIN30
CALL EEPROM_READ
SUBWF P1,W
BTFSC STATUS,Z
GOTO TEST_PIN3_2
; Test par rapport au PIN2
TEST_PIN2
MOVLW PIN20
CALL EEPROM_READ
SUBWF P1,W
BTFSC STATUS,Z
GOTO TEST_PIN2_2
; Test par rapport au PIN1
TEST_PIN1
MOVLW PIN10
CALL EEPROM_READ
SUBWF P1,W
BTFSC STATUS,Z
GOTO TEST_PIN1_2
BAD_PIN
MOVLW 0x63
CALL SEND
MOVLW PINC
CALL EEPROM_READ
MOVWF TEMP
DECF TEMP,F
MOVLW 0xC0
ADDWF TEMP,W
CALL SEND
MOVLW 0x2
SUBWF TEMP,W
BTFSC STATUS,Z
GOTO SET_PIN_2
MOVLW 0x1
SUBWF TEMP,W
BTFSC STATUS,Z
GOTO SET_PIN_1
GOTO LOCK_CARD

TEST_PIN3_2
MOVLW PIN31
CALL EEPROM_READ
SUBWF P2,W
BTFSS STATUS,Z
GOTO TEST_PIN2
MOVLW 0x90
CALL SEND
MOVLW 0x02
CALL SEND
GOTO LOCK_CARD
TEST_PIN2_2
MOVLW PIN21
CALL EEPROM_READ
SUBWF P2,W
BTFSS STATUS,Z
GOTO TEST_PIN1
MOVLW 0x90
CALL SEND
MOVLW 0x01
CALL SEND
MOVLW 0x01
MOVWF PIN2GIVEN
GOTO SET_PIN_3
TEST_PIN1_2
MOVLW PIN11
CALL EEPROM_READ
SUBWF P2,W
BTFSS STATUS,Z
GOTO BAD_PIN
MOVLW 0x90
CALL SEND
MOVLW 0x00
CALL SEND
MOVLW 0x01
MOVWF PIN1GIVEN
GOTO SET_PIN_3

CARD_LOCKED
MOVLW 0x6A
CALL SEND
MOVLW 0x81
CALL SEND
GOTO BOUCLE

; début du programme
INIT
BSF STATUS,RP0 ; select bank1
BCF OPTION_REG,7 ; enable pull-ups
BCF STATUS,RP0
MOVLW 0x00
MOVWF PIN1GIVEN
MOVWF PIN2GIVEN
CALL WAIT1
;Envoi de l'ATR
MOVLW 0x3B ; ATR 3B F7 11 00 01 40 96 54 30 04 0E 6C B6 D6
CALL SEND
MOVLW 0xF7
CALL SEND
MOVLW 0x11
CALL SEND
MOVLW 0x00
CALL SEND
MOVLW 0x01
CALL SEND
MOVLW 0x40
CALL SEND
MOVLW 0x96
CALL SEND
MOVLW 0x54
CALL SEND
MOVLW 0x30
CALL SEND
MOVLW 0x04
CALL SEND
MOVLW 0x0E
CALL SEND
MOVLW 0x6C
CALL SEND
MOVLW 0xB6
CALL SEND
MOVLW 0xD6
CALL SEND
BOUCLE CALL RECEIVE
MOVWF CLA
CALL RECEIVE
MOVWF INS
CALL RECEIVE
MOVWF P1
CALL RECEIVE
MOVWF P2
CALL RECEIVE
MOVWF LE
; Verification que la classe est bien AA
MOVLW 0xAA
SUBWF CLA,W
BTFSS STATUS,Z
GOTO BAD_CLASS
; Verification si la carte est bloquée
MOVLW LOCK
CALL EEPROM_READ
MOVWF TEMPO
MOVLW 0x1
SUBWF TEMPO,W
BTFSC STATUS,Z
GOTO CARD_LOCKED
; Traitement de INS=7 (Présentation du code PIN)
MOVLW 0x7
SUBWF INS,W
BTFSC STATUS,Z
GOTO TESTE_PIN

; Instruction non reconnue
MOVLW 0x68
CALL SEND
MOVLW 0x00
CALL SEND
GOTO BOUCLE
;-----------------------Fin du programme --------------------------

;EEPROM interne
ORG 0x2100
K0 DATA 0xA5 ; key
K1 DATA 0xA4
K2 DATA 0xA4
K3 DATA 0xA4
K4 DATA 0xA4
K5 DATA 0xA4
K6 DATA 0xA4
K7 DATA 0xA4
K8 DATA 0xA4
K9 DATA 0xA4
K10 DATA 0xA4
K11 DATA 0xA4
K12 DATA 0xA4
K13 DATA 0xA4
K14 DATA 0xA4
K15 DATA 0xA5
K16 DATA 0xA4
K17 DATA 0xA4
K18 DATA 0xA4
K19 DATA 0xA4
K20 DATA 0xA4
K21 DATA 0xA4
K22 DATA 0xA4
K23 DATA 0xA4
K24 DATA 0xA4
K25 DATA 0xA4
K26 DATA 0xA4
K27 DATA 0xA4
K28 DATA 0xA4
K29 DATA 0xA4
K30 DATA 0xA4
K31 DATA 0xA4
PIN10 DATA 0x40 ; valeur du PIN1
PIN11 DATA 0x56
PINC DATA 0x3 ; nombre d'essais restants pour PIN
PIN20 DATA 0x23 ; valeur du PIN2
PIN21 DATA 0x76
PIN30 DATA 0x6 ; valeur de PIN3
PIN31 DATA 0x66
LOCK DATA 0x0 ; 0 carte non verrouillée, 1 carte verrouillée
RANDSTART
DATA 0x47
SOFTVERSION
DATA 0x01
END

Publié par Alan Cartman le 2 août 2016

Aucun commentaire: