-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathcard.go
122 lines (103 loc) · 3.09 KB
/
card.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// Package card provides functions for communication with smart cards.
// It includes implementations for handling different types of smart cards
// and reading associated documents.
package card
import (
"errors"
"fmt"
"slices"
"github.com/ebfe/scard"
doc "github.com/ubavic/bas-celik/document"
)
// Represents a physical or virtual smart card.
// Essentially it is just a wrapper for the scard.Card type,
// but it also allows virtual cards which can be useful for testing.
type Card interface {
Status() (*scard.CardStatus, error)
Transmit([]byte) ([]byte, error)
}
// Represents a smart card with a document.
// All types of documents that Bas Celik can read should satisfy this interface
type CardDocument interface {
ReadFile([]byte) ([]byte, error)
InitCard() error
ReadCard() error
GetDocument() (doc.Document, error)
Test() bool
Atr() Atr
}
// Represents a different types of smart card documents.
// Each value of `CardDocumentType` is represented with a struct
// that satisfies `CardDocument` interface.
type CardDocumentType uint8
const (
UnknownDocumentCardType = CardDocumentType(iota)
ApolloIdDocumentCardType
GemaltoIdDocumentCardType
MedicalDocumentCardType
VehicleDocumentCardType
)
var ErrUnknownCard = errors.New("unknown card")
// Detects Card Document from card's ATR
// Ambiguous cases are solved by reading specific card content
func DetectCardDocument(sc Card) (CardDocument, error) {
smartCardStatus, err := sc.Status()
if err != nil {
return nil, fmt.Errorf("reading card status %w", err)
}
atr := Atr(smartCardStatus.Atr)
possibleCardTypes := DetectCardDocumentByAtr(atr)
for _, cardType := range possibleCardTypes {
switch cardType {
case ApolloIdDocumentCardType:
card := &Apollo{atr: atr, smartCard: sc}
return card, nil
case GemaltoIdDocumentCardType:
card := Gemalto{atr: atr, smartCard: sc}
if card.Test() {
return &card, nil
}
case VehicleDocumentCardType:
card := VehicleCard{atr: atr, smartCard: sc}
if card.Test() {
return &card, nil
}
case MedicalDocumentCardType:
card := MedicalCard{atr: atr, smartCard: sc}
if card.Test() {
return &card, nil
}
default:
card := &UnknownDocumentCard{atr: atr, smartCard: sc}
return card, ErrUnknownCard
}
}
return nil, errors.New("unexpected card type")
}
// Reads binary data from the card starting from the specified offset and with the specified length.
func read(card Card, offset, length uint) ([]byte, error) {
readSize := min(length, 0xFF)
apu := buildAPDU(0x00, 0xB0, byte((0xFF00&offset)>>8), byte(offset&0xFF), nil, readSize)
rsp, err := card.Transmit(apu)
if err != nil {
return nil, fmt.Errorf("reading binary: %w", err)
}
if len(rsp) < 2 {
return nil, fmt.Errorf("reading binary: bad status code")
}
return rsp[:len(rsp)-2], nil
}
// Checks if the card response indicates no error.
func responseOK(rsp []byte) bool {
if len(rsp) < 2 {
return false
}
return slices.Equal(rsp[len(rsp)-2:], []byte{0x90, 0x00})
}
// Trim four bytes from the start of the slice
func trim4b(data []byte) []byte {
if len(data) > 4 {
return data[4:]
}
return data
}