import typing
import subprocess
import hashlib
import re
import os
import OpenSSL.crypto as cr
class HashAlg:
__slots__ = ('len', 'hashlib_name', 'mech', 'ident')
def __init__(self, len, hashlib_name, mech, ident):
self.len = len
self.hashlib_name = hashlib_name
self.mech = mech
self.ident = ident
HASH_ALG = {
'SHA1': HashAlg(20, 'sha1', 'SHA1-RSA-PKCS', b'\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14'),
'SHA256': HashAlg(32, 'sha256', 'SHA256-RSA-PKCS', b'\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20'),
'SHA384': HashAlg(48, 'sha384', 'SHA384-RSA-PKCS', b'\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30'),
'SHA512': HashAlg(64, 'sha512', 'SHA512-RSA-PKCS', b'\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40'),
}
class Key:
__slots__ = ('reader', 'id', 'cert_data', 'cert', 'use_rsa_pkcs', 'pin')
reader: str
id: str
cert_data: bytes
cert: typing.Any
use_rsa_pkcs: bool
pin: str
def __init__(self, reader: str, id: str) -> None:
self.reader = reader
self.id = id
cert_data = self.cert_data = subprocess.check_output(['pkcs11-tool', '--slot', reader, '-y', 'cert', '-r', '-d', id])
self.cert = cr.load_certificate(cr.FILETYPE_ASN1, cert_data)
# TODO: autodetect based on key?
self.use_rsa_pkcs = True
def sign(self, msg, hash_alg):
if self.use_rsa_pkcs:
mech = 'RSA-PKCS'
msg = hash_alg.ident + hashlib.new(hash_alg.hashlib_name, msg).digest()
else:
mech = hash_alg.mech
env = os.environ.copy()
env['PIN'] = self.pin
proc = subprocess.run(
['pkcs11-tool', '--slot', self.reader, '--id', self.id, '-m', mech, '--pin', 'env:PIN', '--sign'],
input = msg,
stdout = subprocess.PIPE,
env = env,
check = True,
)
return proc.stdout
class Store:
__slots__ = ('_impl',)
def __init__(self):
self._impl = cr.X509Store()
def load_file(self, path):
self._impl.load_locations(path, None)
def cert_chain(self, key: Key):
ctx = cr.X509StoreContext(self._impl, key.cert)
return ctx.get_verified_chain()
def list() -> typing.Iterable[Key]:
lines = subprocess.check_output(['pkcs11-tool', '--list-slots'], text=True).splitlines()
readers = []
for line in lines:
m = re.fullmatch('Slot ([0-9]+) \\(.*\\): .*', line)
if m is None:
continue
readers.append(m.group(1))
certs = {}
for reader in readers:
lines = subprocess.check_output(['pkcs11-tool', '--slot', reader, '-y', 'cert', '-O'], text=True).splitlines()
for line in lines:
m = re.fullmatch(' ID *: *([0-9a-f]+)$', line)
if m is None:
continue
yield Key(reader, m.group(1))