Skip to content

Instantly share code, notes, and snippets.

@krzys-h
Last active October 16, 2023 09:26
Show Gist options
  • Save krzys-h/579c11d4c5e3b4a69843cca5ea71cb79 to your computer and use it in GitHub Desktop.
Save krzys-h/579c11d4c5e3b4a69843cca5ea71cb79 to your computer and use it in GitHub Desktop.
Reading ELS (Elektroniczna Legitymacja Studencka) data on Linux
# TODO: verify signatures
# TODO: clean up the APDU code
# Some documentation on the ELS format: https://isap.sejm.gov.pl/isap.nsf/download.xsp/WDU20062241634/O/D20061634.pdf
from smartcard.System import readers
from binascii import hexlify, unhexlify
from pyasn1_modules import rfc2315
from pyasn1.codec.der.decoder import decode as der_decoder
from pyasn1.type import univ
import sels
SELECT_FILE = [0x00, 0xA4, 0x04, 0x00, 0x07]
READ_FILE = [0x00, 0xA4, 0x00, 0x00, 0x02]
GET_RESPONSE = [0xA0, 0xC0, 0x00, 0x00]
READ_BINARY = [0x00, 0xB0]
DF_SELS = [0xD6, 0x16, 0x00, 0x00, 0x30, 0x01, 0x01]
EF_CERT = [0x00, 0x01]
EF_ELS = [0x00, 0x02]
for reader in readers():
if "Virtual" in reader.name:
continue
connection = reader.createConnection()
connection.connect()
print("ATR", hexlify(bytes(connection.getATR())).decode())
response, sw1, sw2 = connection.transmit(SELECT_FILE + DF_SELS + [0x00])
assert sw1 == 0x61 # Command successfully executed; ‘XX’ bytes of data are available and can be requested using GET RESPONSE.
num_bytes = sw2
response, sw1, sw2 = connection.transmit(GET_RESPONSE + [num_bytes])
#print(hexlify(bytes([sw1, sw2])).decode(), hexlify(bytes(response)).decode())
response, sw1, sw2 = connection.transmit(READ_FILE + EF_ELS + [0x00])
assert sw1 == 0x61 # Command successfully executed; ‘XX’ bytes of data are available and can be requested using GET RESPONSE.
num_bytes = sw2
response, sw1, sw2 = connection.transmit(GET_RESPONSE + [num_bytes])
#print(hexlify(bytes([sw1, sw2])).decode(), hexlify(bytes(response)).decode())
offset = 0x0000
block_size = 0x80
data = b""
while True:
response, sw1, sw2 = connection.transmit(READ_BINARY + [(offset >> 8) & 0xFF, offset & 0xFF] + [block_size])
#print(hexlify(bytes([sw1, sw2])).decode(), hexlify(bytes(response)).decode())
data += bytes(response)
if sw1 == 0x90:
offset += block_size
elif sw1 == 0x6C: # Bad length value in Le; ‘xx’ is the correct exact Le
block_size = sw2
elif sw1 == 0x6b and sw2 == 0x00: # Wrong parameter(s) P1-P2
break
else:
assert False
print(hexlify(data).decode())
contentInfo, rest_of_input = der_decoder(data, asn1Spec=rfc2315.ContentInfo())
signedData, rest_of_input = der_decoder(contentInfo['content'], asn1Spec=rfc2315.contentTypeMap[contentInfo['contentType']])
infoEncoded, rest_of_input = der_decoder(signedData['contentInfo']['content'], asn1Spec=univ.OctetString())
info, rest_of_input = der_decoder(infoEncoded, asn1Spec=sels.SELSInfo())
print(info)
SELS DEFINITIONS ::= BEGIN
id-SELSInfo OBJECT IDENTIFIER ::= {iso(1) member-body(2) pl(616) organization(1) gov(101) moneas(4) pki(1) sels(1) 1}
SELSInfo ::= SEQUENCE {
wersja INTEGER {v1(1)},
numerSeryjnyUkladu PrintableString (SIZE (8..16)),
nazwaUczelni UTF8String (SIZE (1..128)),
nazwiskoStudenta SEQUENCE OF
UTF8String (SIZE (1..28)),
imionaStudenta SEQUENCE OF
UTF8String (SIZE (1..24)),
numerAlbumu PrintableString (SIZE (1..16)),
numerEdycji PrintableString (SIZE (1)),
numerPesel PrintableString (SIZE (11)),
dataWaznosci GeneralizedTime
}
END
# Auto-generated by asn1ate v.0.6.1.dev0 from sels.asn1
# (last modified on 2022-09-12 00:14:53.138777)
# SELS
from pyasn1.type import univ, char, namedtype, namedval, tag, constraint, useful
def _OID(*components):
output = []
for x in tuple(components):
if isinstance(x, univ.ObjectIdentifier):
output.extend(list(x))
else:
output.append(int(x))
return univ.ObjectIdentifier(output)
class SELSInfo(univ.Sequence):
pass
SELSInfo.componentType = namedtype.NamedTypes(
namedtype.NamedType('wersja', univ.Integer(namedValues=namedval.NamedValues(('v1', 1)))),
namedtype.NamedType('numerSeryjnyUkladu', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(8, 16))),
namedtype.NamedType('nazwaUczelni', char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, 128))),
namedtype.NamedType('nazwiskoStudenta', univ.SequenceOf(componentType=char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, 28)))),
namedtype.NamedType('imionaStudenta', univ.SequenceOf(componentType=char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, 24)))),
namedtype.NamedType('numerAlbumu', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, 16))),
namedtype.NamedType('numerEdycji', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, 1))),
namedtype.NamedType('numerPesel', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(11, 11))),
namedtype.NamedType('dataWaznosci', useful.GeneralizedTime())
)
id_SELSInfo = _OID(1, 2, 616, 1, 101, 4, 1, 1, 1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment