SmartPGP/bin/smartpgp/commands.py

491 lines
15 KiB
Python

# SmartPGP : JavaCard implementation of OpenPGP card v3 specification
# https://c0de.dev/c0de/SmartPGP
# Copyright (C) 2016 ANSSI
# Copyright (C) 2023 Code Fox <SmartPGP@c0de.dev>
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
from smartcard.Exceptions import NoCardException
from smartcard.System import readers
from smartcard.util import toHexString
import struct
import os
import array
import hashlib
SELECT = [0x00, 0xA4, 0x04, 0x00,
0x06,
0xD2, 0x76, 0x00, 0x01, 0x24, 0x01,
0x00]
VERIFY_ADMIN = [0x00, 0x20, 0x00, 0x83]
VERIFY_USER_82 = [0x00, 0x20, 0x00, 0x82]
TERMINATE = [0x00, 0xe6, 0x00, 0x00]
ACTIVATE = [0x00, 0x44, 0x00, 0x00]
ACTIVATE_FULL = [0x00, 0x44, 0x00, 0x01]
GET_SM_CURVE_OID = [0x00, 0xca, 0x00, 0xd4]
GENERATE_ASYMETRIC_KEYPAIR = [0x00, 0x47, 0x80, 0x00]
GET_ASYMETRIC_KEYPAIR = [0x00, 0x47, 0x81, 0x00]
ALGS_ALIASES = {
'ansix9p256r1': 'ansix9p256r1',
'P256': 'ansix9p256r1',
'P-256': 'ansix9p256r1',
'NIST-P256': 'ansix9p256r1',
'ansix9p384r1': 'ansix9p384r1',
'P384': 'ansix9p384r1',
'P-384': 'ansix9p384r1',
'NIST-P384': 'ansix9p384r1',
'ansix9p521r1': 'ansix9p521r1',
'P521': 'ansix9p521r1',
'P-521': 'ansix9p521r1',
'NIST-P521': 'ansix9p521r1',
'brainpoolP256r1': 'brainpoolP256r1',
'BP256': 'brainpoolP256r1',
'BP-256': 'brainpoolP256r1',
'brainpool256': 'brainpoolP256r1',
'brainpoolP384r1': 'brainpoolP384r1',
'BP384': 'brainpoolP384r1',
'BP-384': 'brainpoolP384r1',
'brainpool384': 'brainpoolP384r1',
'brainpoolP512r1': 'brainpoolP512r1',
'BP512': 'brainpoolP512r1',
'BP-512': 'brainpoolP512r1',
'brainpool512': 'brainpoolP512r1',
}
OID_ALGS = {
'ansix9p256r1': [0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07],
'ansix9p384r1': [0x2B, 0x81, 0x04, 0x00, 0x22],
'ansix9p521r1': [0x2B, 0x81, 0x04, 0x00, 0x23],
'brainpoolP256r1': [0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07],
'brainpoolP384r1': [0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B],
'brainpoolP512r1': [0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D],
}
class InvalidBitStringLength(Exception):
pass
class WrongKeyRole(Exception):
pass
class WrongAlgo(Exception):
pass
def ascii_encode_pin(pin):
return [ord(c) for c in pin]
def assemble_with_len(prefix,data):
return prefix + [len(data)] + data
def asOctets(bs):
l = len(bs)
if l%8 != 0:
raise InvalidBitStringLength
result = []
i = 0
while i < l:
byte = 0
for x in range(8):
byte |= bs[i + x] << (7 - x)
result.append(byte)
i += 8
return result
def encode_len(data):
l = len(data)
if l > 0xff:
l = [0x82, (l >> 8) & 0xff, l & 0xff]
elif l > 0x7f:
l = [0x81, l & 0xff]
else:
l = [l & 0xff]
return l
def kdf_itersalted_s2k(salt, value, algo, count):
if algo == 0x08: #SHA256
f = hashlib.new('sha256')
elif algo == 0x0A: #SHA512
f = hashlib.new('sha512')
else:
print("invalid KDF")
salt = list(salt)#[ord(c) for c in salt]
value = list(value)#[ord(c) for c in value]
data = bytes(salt + value)
#data = array.array('B', data).tostring()
datalen = len(data)
while datalen <= count:
f.update(data)
count -= datalen
if 0 < count:
f.update(data[0:count])
return f.digest()
def _raw_send_apdu(connection, text, apdu):
print("%s" % text)
apdu = [int(c) for c in apdu]
#print ' '.join('{:02X}'.format(c) for c in apdu)
(data, sw1, sw2) = connection.transmit(apdu)
#print ' '.join('{:02X}'.format(c) for c in data)
print("%02X %02X" % (sw1, sw2))
return (data,sw1,sw2)
def list_readers():
for reader in readers():
try:
connection = reader.createConnection()
connection.connect()
print(reader, toHexString(connection.getATR()))
except NoCardException:
print(reader, 'no card inserted')
def select_reader(reader_index):
reader_list = readers()
r = reader_list[reader_index]
conn = r.createConnection()
conn.connect()
return conn
def select_applet(connection):
return _raw_send_apdu(connection,"Select OpenPGP Applet",SELECT)
def verif_admin_pin(connection, admin_pin):
verif_apdu = assemble_with_len(VERIFY_ADMIN,ascii_encode_pin(admin_pin))
return _raw_send_apdu(connection,"Verify Admin PIN",verif_apdu)
def verif_user_pin(connection, user_pin):
verif_apdu = assemble_with_len(VERIFY_USER_82,ascii_encode_pin(user_pin))
return _raw_send_apdu(connection,"Verify User PIN",verif_apdu)
def full_reset_card(connection):
_raw_send_apdu(connection,"Terminate",TERMINATE)
_raw_send_apdu(connection,"Activate",ACTIVATE_FULL)
def reset_card(connection):
_raw_send_apdu(connection,"Terminate",TERMINATE)
_raw_send_apdu(connection,"Activate",ACTIVATE)
def switch_crypto_rsa_2048(connection,key_role):
data = [
0x01, # RSA
0x08, 0x00, # 2048 bits modulus
0x00, 0x11, # 65537 - 17 bits public exponent
0x03] # crt form with modulus
if key_role == 'sig':
role = 0xc1
elif key_role == 'dec':
role = 0xc2
elif key_role == 'auth':
role = 0xc3
elif key_role == 'sm':
role = 0xd4
else:
raise WrongKeyRole
prefix = [0x00, 0xDA, 0x00] + [role]
apdu = assemble_with_len(prefix, data)
_raw_send_apdu(connection,"Switch to RSA2048 (%s)" % (key_role,),apdu)
def switch_crypto_rsa_3072(connection,key_role):
data = [
0x01, # RSA
0x0C, 0x00, # 3072 bits modulus
0x00, 0x11, # 65537 - 17 bits public exponent
0x03] # crt form with modulus
if key_role == 'sig':
role = 0xc1
elif key_role == 'dec':
role = 0xc2
elif key_role == 'auth':
role = 0xc3
elif key_role == 'sm':
role = 0xd4
else:
raise WrongKeyRole
prefix = [0x00, 0xDA, 0x00] + [role]
apdu = assemble_with_len(prefix, data)
_raw_send_apdu(connection,"Switch to RSA3072 (%s)" % (key_role,),apdu)
def switch_crypto_rsa_4096(connection,key_role):
data = [
0x01, # RSA
0x10, 0x00, # 4096 bits modulus
0x00, 0x11, # 65537 - 17 bits public exponent
0x03] # crt form with modulus
if key_role == 'sig':
role = 0xc1
elif key_role == 'dec':
role = 0xc2
elif key_role == 'auth':
role = 0xc3
elif key_role == 'sm':
role = 0xd4
else:
raise WrongKeyRole
prefix = [0x00, 0xDA, 0x00] + [role]
apdu = assemble_with_len(prefix, data)
_raw_send_apdu(connection,"Switch to RSA4096 (%s)" % (key_role,),apdu)
def switch_crypto(connection,crypto,key_role):
alg_name = None
role = None
# treat RSA differently
if crypto=='rsa2048' or crypto=='RSA2048' or crypto=='rsa' or crypto=='RSA':
return switch_crypto_rsa_2048(connection,key_role)
if crypto=='rsa3072' or crypto=='RSA3072' or crypto=='rsa' or crypto=='RSA':
return switch_crypto_rsa_3072(connection,key_role)
if crypto=='rsa4096' or crypto=='RSA4096' or crypto=='rsa' or crypto=='RSA':
return switch_crypto_rsa_4096(connection,key_role)
# this code is only for elliptic curves
try:
alg_name = ALGS_ALIASES[crypto]
except KeyError:
raise WrongAlgo
data = OID_ALGS[alg_name]
byte1 = 0x12
if key_role == 'sig':
role = 0xc1
byte1 = 0x13
elif key_role == 'dec':
role = 0xc2
elif key_role == 'auth':
role = 0xc3
elif key_role == 'sm':
role = 0xd4
else:
raise WrongKeyRole
prefix = [0x00, 0xDA, 0x00] + [role]
apdu = assemble_with_len(prefix, [byte1] + data + [0xff])
_raw_send_apdu(connection,"Switch to %s (%s)" % (crypto,key_role),apdu)
def generate_sm_key(connection):
apdu = assemble_with_len(GENERATE_ASYMETRIC_KEYPAIR, [0xA6, 0x00])
apdu = apdu + [0x00]
return _raw_send_apdu(connection,"Generate SM key",apdu)
def get_sm_key(connection):
apdu = assemble_with_len(GET_ASYMETRIC_KEYPAIR, [0xA6, 0x00])
apdu = apdu + [0x00]
return _raw_send_apdu(connection,"Get SM key",apdu)
def set_resetting_code(connection, resetting_code):
apdu = assemble_with_len([0x00, 0xDA, 0x00, 0xD3], ascii_encode_pin(resetting_code))
_raw_send_apdu(connection,"Define the resetting code (PUK)",apdu)
def unblock_pin(connection, resetting_code, new_user_pin):
data = ascii_encode_pin(resetting_code)+ascii_encode_pin(new_user_pin)
apdu = assemble_with_len([0x00, 0x2C, 0x00, 0x81], data)
_raw_send_apdu(connection,"Unblock user PIN with resetting code",apdu)
def put_sm_key(connection, pubkey, privkey):
ins_p1_p2 = [0xDB, 0x3F, 0xFF]
cdata = [0x92] + encode_len(privkey) + [0x99] + encode_len(pubkey)
cdata = [0xA6, 0x00, 0x7F, 0x48] + encode_len(cdata) + cdata
cdata = cdata + [0x5F, 0x48] + encode_len(privkey + pubkey) + privkey + pubkey
cdata = [0x4D] + encode_len(cdata) + cdata
i = 0
cl = 255
l = len(cdata)
while i < l:
if (l - i) <= cl:
cla = 0x00
data = cdata[i:]
i = l
else:
cla = 0x10
data = cdata[i:i+cl]
i = i + cl
apdu = assemble_with_len([cla] + ins_p1_p2, data)
_raw_send_apdu(connection,"Sending SM key chunk",apdu)
def put_sign_certificate(connection, cert):
prefix = [0x00, 0xA5, 0x02, 0x04]
data = [0x60, 0x04, 0x5C, 0x02, 0x7F, 0x21]
apdu = assemble_with_len(prefix, data)
_raw_send_apdu(connection,"Selecting SIGN certificate",apdu)
ins_p1_p2 = [0xDA, 0x7F, 0x21]
i = 0
cl = 255
l = len(cert)
while i < l:
if (l - i) <= cl:
cla = 0x00
data = cert[i:]
i = l
else:
cla = 0x10
data = cert[i:i+cl]
i = i + cl
apdu = assemble_with_len([cla] + ins_p1_p2, data)
_raw_send_apdu(connection,"Sending SIGN certificate chunk",apdu)
def put_auth_certificate(connection, cert):
prefix = [0x00, 0xA5, 0x00, 0x04]
data = [0x60, 0x04, 0x5C, 0x02, 0x7F, 0x21]
apdu = assemble_with_len(prefix, data)
_raw_send_apdu(connection,"Selecting AUTH certificate",apdu)
ins_p1_p2 = [0xDA, 0x7F, 0x21]
i = 0
cl = 255
l = len(cert)
while i < l:
if (l - i) <= cl:
cla = 0x00
data = cert[i:]
i = l
else:
cla = 0x10
data = cert[i:i+cl]
i = i + cl
apdu = assemble_with_len([cla] + ins_p1_p2, data)
_raw_send_apdu(connection,"Sending AUTH certificate chunk",apdu)
def put_sm_certificate(connection, cert):
prefix = [0x00, 0xA5, 0x03, 0x04]
data = [0x60, 0x04, 0x5C, 0x02, 0x7F, 0x21]
apdu = assemble_with_len(prefix, data)
_raw_send_apdu(connection,"Selecting SM certificate",apdu)
ins_p1_p2 = [0xDA, 0x7F, 0x21]
i = 0
cl = 255
l = len(cert)
while i < l:
if (l - i) <= cl:
cla = 0x00
data = cert[i:]
i = l
else:
cla = 0x10
data = cert[i:i+cl]
i = i + cl
apdu = assemble_with_len([cla] + ins_p1_p2, data)
_raw_send_apdu(connection,"Sending SM certificate chunk",apdu)
def get_sm_certificate(connection):
prefix = [0x00, 0xA5, 0x03, 0x04]
data = [0x60, 0x04, 0x5C, 0x02, 0x7F, 0x21]
apdu = assemble_with_len(prefix, data)
_raw_send_apdu(connection,"Selecting SM certificate",apdu)
apdu = [0x00, 0xCA, 0x7F, 0x21, 0x00]
(data,sw1,sw2) = _raw_send_apdu(connection,"Receiving SM certificate chunk",apdu)
while sw1 == 0x61:
apdu = [0x00, 0xC0, 0x00, 0x00, sw2]
(ndata,sw1,sw2) = _raw_send_apdu(connection,"Receiving SM certificate chunk",apdu)
data = data + ndata
return (data,sw1,sw2)
def get_sm_curve_oid(connection):
""" Get Curve OID for Secure Messaging
Return Curve OID (DER-encoded)
"""
apdu = GET_SM_CURVE_OID + [0x00]
(data,sw1,sw2) = _raw_send_apdu(connection,"SM Curve OID",apdu)
b = bytearray(data)
assert(b[0]==0xd4)
curve_len = b[1]
curve = b[2:]
assert(curve_len == len(curve))
assert(curve[0])==0x12
curve = curve[1:]
if curve[-1] == 0xff:
curve.pop()
#print ' '.join('{:02X}'.format(c) for c in curve)
# Add DER OID header manually ...
return '\x06' + struct.pack('B',len(curve)) + curve
def put_aes_key(connection, key):
prefix = [0x00, 0xDA, 0x00, 0xD5]
data = key
apdu = assemble_with_len(prefix, data)
_raw_send_apdu(connection,"Put AES key",apdu)
def encrypt_aes(connection, msg):
ins_p1_p2 = [0x2A, 0x86, 0x80]
i = 0
cl = 255
l = len(msg)
while i < l:
if (l - i) <= cl:
cla = 0x00
data = msg[i:]
i = l
else:
cla = 0x10
data = msg[i:i+cl]
i = i + cl
apdu = assemble_with_len([cla] + ins_p1_p2, data)
(res,sw1,sw2) = _raw_send_apdu(connection,"Encrypt AES chunk",apdu)
while sw1 == 0x61:
apdu = [0x00, 0xC0, 0x00, 0x00, sw2]
(nres,sw1,sw2) = _raw_send_apdu(connection,"Receiving encrypted chunk",apdu)
res = res + nres
return (res[1:],sw1,sw2)
def decrypt_aes(connection, msg):
ins_p1_p2 = [0x2A, 0x80, 0x86]
i = 0
cl = 255
msg = [0x02] + msg
l = len(msg)
while i < l:
if (l - i) <= cl:
cla = 0x00
data = msg[i:]
i = l
else:
cla = 0x10
data = msg[i:i+cl]
i = i + cl
apdu = assemble_with_len([cla] + ins_p1_p2, data)
(res,sw1,sw2) = _raw_send_apdu(connection,"Decrypt AES chunk",apdu)
while sw1 == 0x61:
apdu = [0x00, 0xC0, 0x00, 0x00, sw2]
(nres,sw1,sw2) = _raw_send_apdu(connection,"Receiving decrypted chunk",apdu)
res = res + nres
return (res,sw1,sw2)
def put_kdf_do(connection, kdf_do):
prefix = [0x00, 0xDA, 0x00, 0xF9]
data = kdf_do
apdu = assemble_with_len(prefix, data)
_raw_send_apdu(connection,"Put KDF-DO",apdu)
def get_kdf_do(connection):
apdu = [0x00, 0xCA, 0x00, 0xF9, 0x00]
(data,sw1,sw2) = _raw_send_apdu(connection,"Get KDF-DO",apdu)
return (data,sw1,sw2)
def change_reference_data_pw1(connection, old, new):
prefix = [0x00, 0x24, 0x00, 0x81]
data = old + new
apdu = assemble_with_len(prefix, data)
(_,sw1,sw2) = _raw_send_apdu(connection,"Change PW1", apdu)
return (sw1,sw2)
def change_reference_data_pw3(connection, old, new):
prefix = [0x00, 0x24, 0x00, 0x83]
data = old + new
apdu = assemble_with_len(prefix, data)
(_,sw1,sw2) = _raw_send_apdu(connection,"Change PW3", apdu)
return (sw1,sw2)