Initial commit
This commit is contained in:
0
bin/smartpgp/__init__.py
Normal file
0
bin/smartpgp/__init__.py
Normal file
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
|
234
bin/smartpgp/highlevel.py
Normal file
234
bin/smartpgp/highlevel.py
Normal file
@@ -0,0 +1,234 @@
|
||||
|
||||
# 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 commands import *
|
||||
|
||||
import binascii
|
||||
import pyasn1
|
||||
from pyasn1.type import univ
|
||||
from pyasn1.codec.der import encoder as der_encoder,decoder as der_decoder
|
||||
|
||||
class ConnectionFailed(Exception):
|
||||
pass
|
||||
|
||||
class AdminPINFailed(Exception):
|
||||
pass
|
||||
|
||||
class CardConnectionContext:
|
||||
|
||||
def __init__(self):
|
||||
self.reader_index = 0
|
||||
self.admin_pin = "12345678"
|
||||
self.connection = None
|
||||
self.read_pin = self._default_pin_read_function
|
||||
self.connected = False
|
||||
self.verified = False
|
||||
self.input = None
|
||||
|
||||
def _default_pin_read_function(self, pin_type):
|
||||
return self.admin_pin
|
||||
|
||||
def set_pin_read_function(self, fun):
|
||||
self.read_pin = fun
|
||||
|
||||
def verify_admin_pin(self):
|
||||
if self.verified:
|
||||
return
|
||||
admin_pin = self.read_pin("Admin")
|
||||
(_,sw1,sw2)=verif_admin_pin(self.connection, admin_pin)
|
||||
if sw1==0x90 and sw2==0x00:
|
||||
self.verified = True
|
||||
else:
|
||||
raise AdminPINFailed
|
||||
|
||||
def connect(self):
|
||||
if self.connected:
|
||||
return
|
||||
self.connection = select_reader(self.reader_index)
|
||||
(_,sw1,sw2)=select_applet(self.connection)
|
||||
if sw1==0x90 and sw2==0x00:
|
||||
self.connected = True
|
||||
else:
|
||||
raise ConnectionFailed
|
||||
|
||||
def cmd_list_readers(self):
|
||||
list_readers()
|
||||
|
||||
def cmd_full_reset(self):
|
||||
# ignore errors
|
||||
self.connection = select_reader(self.reader_index)
|
||||
select_applet(self.connection)
|
||||
# do not use self.verify_admin_pin(), we want to force sending the APDUs
|
||||
verif_admin_pin(self.connection, self.admin_pin)
|
||||
verif_admin_pin(self.connection, self.admin_pin)
|
||||
verif_admin_pin(self.connection, self.admin_pin)
|
||||
full_reset_card(self.connection)
|
||||
# force re-entering admin PIN
|
||||
self.verified = False
|
||||
|
||||
def cmd_reset(self):
|
||||
# ignore errors
|
||||
self.connection = select_reader(self.reader_index)
|
||||
select_applet(self.connection)
|
||||
# do not use self.verify_admin_pin(), we want to force sending the APDUs
|
||||
verif_admin_pin(self.connection, self.admin_pin)
|
||||
verif_admin_pin(self.connection, self.admin_pin)
|
||||
verif_admin_pin(self.connection, self.admin_pin)
|
||||
reset_card(self.connection)
|
||||
# force re-entering admin PIN
|
||||
self.verified = False
|
||||
|
||||
def cmd_switch_crypto(self,alg_name,key_role):
|
||||
self.connect()
|
||||
self.verify_admin_pin()
|
||||
switch_crypto(self.connection,alg_name,key_role)
|
||||
|
||||
def cmd_switch_all_crypto(self,alg_name):
|
||||
self.connect()
|
||||
self.verify_admin_pin()
|
||||
switch_crypto(self.connection,alg_name,'sig')
|
||||
switch_crypto(self.connection,alg_name,'dec')
|
||||
switch_crypto(self.connection,alg_name,'auth')
|
||||
|
||||
def cmd_switch_bp256(self):
|
||||
self.cmd_switch_all_crypto('brainpoolP256r1')
|
||||
|
||||
def cmd_switch_bp384(self):
|
||||
self.cmd_switch_all_crypto('brainpoolP384r1')
|
||||
|
||||
def cmd_switch_bp512(self):
|
||||
self.cmd_switch_all_crypto('brainpoolP512r1')
|
||||
|
||||
def cmd_switch_p256(self):
|
||||
self.cmd_switch_all_crypto('P-256')
|
||||
|
||||
def cmd_switch_p384(self):
|
||||
self.cmd_switch_all_crypto('P-384')
|
||||
|
||||
def cmd_switch_p521(self):
|
||||
self.cmd_switch_all_crypto('P-521')
|
||||
|
||||
def cmd_switch_rsa2048(self):
|
||||
self.cmd_switch_all_crypto('rsa2048')
|
||||
|
||||
def cmd_generate_sm_key(self):
|
||||
if not self.output:
|
||||
print "Missing output file name"
|
||||
return
|
||||
self.connect()
|
||||
self.verify_admin_pin()
|
||||
(data,sw1,sw2) = generate_sm_key(self.connection)
|
||||
if sw1!=0x90 or sw2!=0x00:
|
||||
print "generate_sm_key failed"
|
||||
return
|
||||
if len(data) < 4 or data[0]!=0x7f or data[1]!=0x49:
|
||||
print "Strange reply for get_sm_certificate"
|
||||
return
|
||||
blob_len = data[2]
|
||||
blob = data[3:]
|
||||
assert(blob_len == len(blob))
|
||||
if blob[0]!=0x86:
|
||||
print "get_sm_certificate return something not a public key"
|
||||
return
|
||||
assert(blob[1]==len(blob[2:]))
|
||||
pubkey = blob[2:]
|
||||
# get curve OID
|
||||
curve_oid_der = get_sm_curve_oid(self.connection)
|
||||
if not curve_oid_der:
|
||||
print "Error getting SM curve OID"
|
||||
return
|
||||
(curve_oid,_) = der_decoder.decode(str(curve_oid_der))
|
||||
# now format it to DER [RFC5480]
|
||||
s = univ.Sequence()
|
||||
oid_elliptic_curve_pubkey = univ.ObjectIdentifier('1.2.840.10045.2.1')
|
||||
s.setComponentByPosition(0,oid_elliptic_curve_pubkey)
|
||||
s.setComponentByPosition(1,curve_oid)
|
||||
bs = univ.BitString("'%s'H" % binascii.hexlify(bytearray(pubkey)))
|
||||
s2 = univ.Sequence()
|
||||
s2.setComponentByPosition(0,s)
|
||||
s2.setComponentByPosition(1,bs)
|
||||
pubkey_der = der_encoder.encode(s2)
|
||||
print binascii.hexlify(pubkey_der)
|
||||
# and write result
|
||||
with open(self.output,"wb") as f:
|
||||
f.write(pubkey_der)
|
||||
f.close()
|
||||
|
||||
def cmd_put_sm_key(self):
|
||||
if self.input is None:
|
||||
print "No input key file"
|
||||
return
|
||||
f = open(self.input, 'r')
|
||||
fstr = f.read()
|
||||
f.close()
|
||||
(der,_) = der_decoder.decode(fstr)
|
||||
privkey = [ord(c) for c in der[1].asOctets()]
|
||||
oid = bytearray(der_encoder.encode(der[2]))
|
||||
pubkey = asOctets(der[3])
|
||||
if oid[0] == 0xa0:
|
||||
oid = oid[2:]
|
||||
oid_len = oid[1]
|
||||
oid = oid[2:]
|
||||
assert(oid_len == len(oid))
|
||||
curve = None
|
||||
for k,v in OID_ALGS.items():
|
||||
if bytearray(v) == oid:
|
||||
curve = k
|
||||
if curve is None:
|
||||
print "Curve not supported (%s)" % der[2]
|
||||
return
|
||||
self.connect()
|
||||
self.verify_admin_pin()
|
||||
switch_crypto(self.connection, curve, 'sm')
|
||||
put_sm_key(self.connection, pubkey, privkey)
|
||||
|
||||
def cmd_set_resetting_code(self):
|
||||
self.connect()
|
||||
self.verify_admin_pin()
|
||||
resetting_code = self.read_pin("PUK")
|
||||
set_resetting_code(self.connection, resetting_code)
|
||||
|
||||
def cmd_unblock_pin(self):
|
||||
self.connect()
|
||||
resetting_code = self.read_pin("PUK")
|
||||
new_user_pin = self.read_pin("new user")
|
||||
unlock_pin(self.connection, resetting_code, new_user_pin)
|
||||
|
||||
def cmd_put_sm_certificate(self):
|
||||
if self.input is None:
|
||||
print "No input certificate file"
|
||||
return
|
||||
f = open(self.input, 'r')
|
||||
cert = f.read()
|
||||
cert = [ord(c) for c in cert]
|
||||
f.close()
|
||||
self.connect()
|
||||
self.verify_admin_pin()
|
||||
put_sm_certificate(self.connection,cert)
|
||||
|
||||
def cmd_get_sm_certificate(self):
|
||||
if self.output is None:
|
||||
print "No output file"
|
||||
return
|
||||
self.connect()
|
||||
(cert,_,_) = get_sm_certificate(self.connection)
|
||||
cert = "".join([chr(c) for c in cert])
|
||||
with open(self.output, 'w') as f:
|
||||
f.write(cert)
|
||||
f.close()
|
Reference in New Issue
Block a user