Initial commit
This commit is contained in:
284
bin/smartpgp/commands.py
Normal file
284
bin/smartpgp/commands.py
Normal file
@@ -0,0 +1,284 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# SmartPGP : JavaCard implementation of OpenPGP card v3 specification
|
||||
# https://github.com/ANSSI-FR/smartpgp
|
||||
# Copyright (C) 2016 ANSSI
|
||||
|
||||
# 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
|
||||
|
||||
SELECT = [0x00, 0xA4, 0x04, 0x00,
|
||||
0x06,
|
||||
0xD2, 0x76, 0x00, 0x01, 0x24, 0x01,
|
||||
0x00]
|
||||
|
||||
VERIFY = [0x00, 0x20, 0x00, 0x83]
|
||||
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]
|
||||
|
||||
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 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 is not 0:
|
||||
raise "BitString length is not a multiple of 8"
|
||||
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 _raw_send_apdu(connection, text, apdu):
|
||||
print "%s" % text
|
||||
data, sw1, sw2 = connection.transmit(apdu)
|
||||
print data
|
||||
print "%s: %02X %02X" % (text, 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,ascii_encode_pin(admin_pin))
|
||||
return _raw_send_apdu(connection,"Verify Admin 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(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(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(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 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_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, 0x00]
|
||||
(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
|
Reference in New Issue
Block a user